diff --git a/boot/src/main/java/com/zfoo/boot/StorageAutoConfiguration.java b/boot/src/main/java/com/zfoo/boot/StorageAutoConfiguration.java index a9fabae5..fd621377 100644 --- a/boot/src/main/java/com/zfoo/boot/StorageAutoConfiguration.java +++ b/boot/src/main/java/com/zfoo/boot/StorageAutoConfiguration.java @@ -12,13 +12,24 @@ package com.zfoo.boot; +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.StringUtils; import com.zfoo.storage.StorageContext; import com.zfoo.storage.manager.StorageManager; +import com.zfoo.storage.model.anno.GraalvmNativeResource; import com.zfoo.storage.model.config.StorageConfig; +import com.zfoo.storage.model.resource.ResourceData; +import com.zfoo.storage.model.resource.ResourceEnum; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.hint.*; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportRuntimeHints; + +import java.util.HashSet; /** * @author godotg @@ -26,8 +37,11 @@ import org.springframework.context.annotation.Configuration; */ @Configuration(proxyBeanMethods = false) @ConditionalOnBean(StorageConfig.class) +@ImportRuntimeHints(StorageAutoConfiguration.StorageHints.class) public class StorageAutoConfiguration { + private static final Logger logger = LoggerFactory.getLogger(StorageAutoConfiguration.class); + @Bean @ConditionalOnMissingBean @ConditionalOnBean(StorageConfig.class) @@ -43,4 +57,46 @@ public class StorageAutoConfiguration { return new StorageContext(); } + + // Register runtime hints for the token library + public static class StorageHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + logger.info("storage aot runtime hints register"); + + var classes = new HashSet>(); + classes.add(ResourceData.class); + + try { + for (var className : ClassUtils.getAllClasses("")) { + try { + var clazz = Class.forName(className); + if (!clazz.isAnnotationPresent(GraalvmNativeResource.class)) { + continue; + } + classes.add(clazz); + classes.addAll(ClassUtils.relevantClass(clazz)); + } catch (Throwable t) { + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + for (var clazz : classes) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), clazz); + logger.info("storage aot hints register serialization [{}]", clazz); + } + + for (var resource : ResourceEnum.values()) { + var include = StringUtils.format("*.{}", resource.getType()); + hints.resources().registerPattern(include); + logger.info("storage aot hints register resources [{}]", include); + } + } + } + } diff --git a/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java b/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java index af7698d0..2a25d4dc 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java @@ -22,17 +22,19 @@ import com.zfoo.protocol.util.ReflectionUtils; import com.zfoo.protocol.util.StringUtils; import com.zfoo.storage.StorageContext; import com.zfoo.storage.model.anno.Id; +import com.zfoo.storage.model.anno.GraalvmNativeResource; import com.zfoo.storage.model.anno.ResInjection; import com.zfoo.storage.model.config.StorageConfig; import com.zfoo.storage.model.resource.ResourceEnum; import com.zfoo.storage.model.vo.ResourceDef; import com.zfoo.storage.model.vo.Storage; +import com.zfoo.util.GraalVmUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.stereotype.Component; @@ -82,20 +84,12 @@ public class StorageManager implements IStorageManager { public void initBefore() { var resourceDefinitionMap = new HashMap, ResourceDef>(); - // 扫描Excel的class类文件 - var clazzNameSet = scanResourceAnno(StringUtils.tokenize(storageConfig.getScanPackage(), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); + // 获取需要被映射的Excel的class类文件 + var clazzSet = resourceClass(); // 通过class类文件扫描excel文件地址 - for (var clazzName : clazzNameSet) { - Class resourceClazz; - try { - resourceClazz = Class.forName(clazzName); - } catch (ClassNotFoundException e) { - // 无法获取资源类 - throw new RuntimeException(StringUtils.format("Unable to get resource [class:{}]", clazzName)); - } - - var resourceFile = scanResourceFile(resourceClazz); + for (var resourceClazz : clazzSet) { + var resourceFile = resource(resourceClazz); ResourceDef resourceDef = new ResourceDef(resourceClazz, resourceFile); if (resourceDefinitionMap.containsKey(resourceClazz)) { // 类的资源定义已经存在 @@ -148,7 +142,8 @@ public class StorageManager implements IStorageManager { var applicationContext = StorageContext.getApplicationContext(); var componentBeans = applicationContext.getBeansWithAnnotation(Component.class); for (var bean : componentBeans.values()) { - ReflectionUtils.filterFieldsInClass(bean.getClass(), field -> field.isAnnotationPresent(ResInjection.class), field -> { + var clazz = bean.getClass(); + ReflectionUtils.filterFieldsInClass(clazz, field -> field.isAnnotationPresent(ResInjection.class), field -> { Type type = field.getGenericType(); if (!(type instanceof ParameterizedType)) { @@ -223,12 +218,23 @@ public class StorageManager implements IStorageManager { return getStorageConfig(); } - private Set scanResourceAnno(String[] scanPackages) { - var resourcePatternResolver = new PathMatchingResourcePatternResolver(); - var metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + private Set> resourceClass() { + // graalvm环境 PathMatchingResourcePatternResolver/CachingMetadataReaderFactory 无法使用,所以直接在spring容器中获取 + if (GraalVmUtils.isGraalVM()) { + var applicationContext = StorageContext.getApplicationContext(); + var nativeResources = applicationContext.getBeansWithAnnotation(GraalvmNativeResource.class); + return nativeResources.values().stream().map(it -> it.getClass()).collect(Collectors.toSet()); + } else { + return scanResourceAnno(); + } + } + private Set> scanResourceAnno() { + var scanPackages = StringUtils.tokenize(storageConfig.getScanPackage(), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); + var result = new HashSet>(); try { - var result = new HashSet(); + var resourcePatternResolver = new PathMatchingResourcePatternResolver(); + var metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); for (var scanPackage : scanPackages) { var packageSearchPath = "classpath*:" + scanPackage.replace(StringUtils.PERIOD, StringUtils.SLASH) + StringUtils.SLASH + SUFFIX_PATTERN; var resources = resourcePatternResolver.getResources(packageSearchPath); @@ -239,17 +245,28 @@ public class StorageManager implements IStorageManager { var annoMeta = metadataReader.getAnnotationMetadata(); if (annoMeta.hasAnnotation(resourceName)) { ClassMetadata clazzMeta = metadataReader.getClassMetadata(); - result.add(clazzMeta.getClassName()); + Class resourceClazz = Class.forName(clazzMeta.getClassName()); + result.add(resourceClazz); } } } } return result; - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException("Unable to read resource information", e); } } + private Resource resource(Class clazz) { + if (GraalVmUtils.isGraalVM()) { + var resourceLoader = new DefaultResourceLoader(); + var resource = resourceLoader.getResource(clazz.getAnnotation(GraalvmNativeResource.class).value()); + return resource; + } else { + return scanResourceFile(clazz); + } + } + private Resource scanResourceFile(Class clazz) { var resourcePatternResolver = new PathMatchingResourcePatternResolver(); var metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/ExcelFieldName.java b/storage/src/main/java/com/zfoo/storage/model/anno/ExcelFieldName.java index 07ba6127..3630e9ef 100644 --- a/storage/src/main/java/com/zfoo/storage/model/anno/ExcelFieldName.java +++ b/storage/src/main/java/com/zfoo/storage/model/anno/ExcelFieldName.java @@ -1,13 +1,17 @@ package com.zfoo.storage.model.anno; +import org.springframework.aot.hint.annotation.Reflective; + import java.lang.annotation.*; /** - * 指定文件列名,不指定则默认列名与字段名一致 + * EN: Specify the file column name. If not specified, the default column name will be consistent with the field name. + * CN: 指定文件列名,不指定则默认列名与字段名一致 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) +@Reflective public @interface ExcelFieldName { String value(); } diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/GraalvmNativeResource.java b/storage/src/main/java/com/zfoo/storage/model/anno/GraalvmNativeResource.java new file mode 100644 index 00000000..c0840956 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/anno/GraalvmNativeResource.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package com.zfoo.storage.model.anno; + +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +/** + * graalvm build must use this annotation to specify path, because native build cannot scan classes and files + * + * @author godotg + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Reflective +@Component +public @interface GraalvmNativeResource { + + /** + * graalvm build must specify resource path + */ + String value(); + +} diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/Id.java b/storage/src/main/java/com/zfoo/storage/model/anno/Id.java index ac9742c2..3963594b 100644 --- a/storage/src/main/java/com/zfoo/storage/model/anno/Id.java +++ b/storage/src/main/java/com/zfoo/storage/model/anno/Id.java @@ -14,10 +14,12 @@ package com.zfoo.storage.model.anno; +import org.springframework.aot.hint.annotation.Reflective; + import java.lang.annotation.*; /** - * 主键 + * primary key * * @author godotg * @version 3.0 @@ -26,5 +28,6 @@ import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) +@Reflective public @interface Id { } diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/Index.java b/storage/src/main/java/com/zfoo/storage/model/anno/Index.java index 176ce768..6f25a4db 100644 --- a/storage/src/main/java/com/zfoo/storage/model/anno/Index.java +++ b/storage/src/main/java/com/zfoo/storage/model/anno/Index.java @@ -13,10 +13,13 @@ package com.zfoo.storage.model.anno; +import org.springframework.aot.hint.annotation.Reflective; + import java.lang.annotation.*; /** - * 索引,索引的名称使用字段属性的名称,用HaspMap实现 + * EN: the name of the index uses the name of the field attribute, implemented with HaspMap + * CN: 索引的名称使用字段属性的名称,用HaspMap实现 * * @author godotg * @version 3.0 @@ -25,6 +28,7 @@ import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) +@Reflective public @interface Index { boolean unique() default false; diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java b/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java index 0b6b44a4..6fe47e2b 100644 --- a/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java +++ b/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java @@ -18,7 +18,8 @@ import org.springframework.aot.hint.annotation.Reflective; import java.lang.annotation.*; /** - * 静态数据的注入 + * EN: Injection of static data + * CN: 静态数据的注入 * * @author godotg * @version 3.0 diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java b/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java index eafedbe8..728b541d 100644 --- a/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java +++ b/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java @@ -18,9 +18,9 @@ import org.springframework.aot.hint.annotation.Reflective; import java.lang.annotation.*; /** - * 资源注解 - * 可以指定对应的资源文件名(只指定文件名,不需要文件后缀) - * 如果不指定资源文件名,则默认通过扫描路径获取与类名相同的文件资源 + * + * EN: You can specify the corresponding resource file name (only the file name is specified, no file suffix is required) + * CN: 可以指定对应的资源文件名(只指定文件名,不需要文件后缀),如果不指定资源文件名,则默认通过扫描路径获取与类名相同的文件资源 * * @author godotg * @version 3.0 @@ -31,4 +31,9 @@ import java.lang.annotation.*; @Reflective public @interface Resource { String alias() default ""; + + /** + * graalvm build must specified path + */ + String nativeClassPath() default ""; }