GraalVM实践image与springnative
native-image
GraalVM是从main方法开始分析,所有可达的类和方法会被记录。
但是反射、动态代理、资源文件等无法通过简单的静态分析捕获,为了支持这部分代码的行为,GraalVM支持配置文件来支持JIT时常用的做法。
GraalVM推荐在"META-INF/native-image/"(或子文件夹,如为避免不同jar下的配置冲突,放在"META-INF/native-image/{groupId}/{artifactId}/")下的native-image.properties来形成构建本地代码所需要的命令行参数。native-images.properties一般包含哪些类可以在构建时初始化(--initialize-at-build-time),哪些只能运行时初始化(--initialize-at-run-time)。
有被JDK动态代理的接口,可以记录到proxy-config.json,比如MyBatis的SqlSession,Spring的Component等注解。
有JNI调用的类,可以记录到jni-config.json,比如sun.instrument.InstrumentationImpl。
有要被反射调用的,可以记录到reflect-config.json,比如我们的Mapper要被MyBatis代理,要求Mapper接口的方法可以被反射查询到,参数和返回要被反射调用getter/setter来取值/设置值。
有资源文件,可以记录到resource-config.json,比如messages.properties这种国际化资源、application.proeprties这种应用配置以及MyBatis的Mapper文件等等。
还有序列化相关的,可以记录到serialization-config.json。
这些配置文件可以通过GraalVM提供的native-image-agent来自动生成绝大部分内容:-agentlib:native-image-agent=config-output-dir=path/file
然而,native-image.properties却不会自动生成,需要我们手动添加。如果不想使用默认的配置文件名,可以在修改文件名后(比如proxy-config.json改成proxy.json,甚至放在子文件夹下),使用参数告知graalvm。native-image.properties示例如下:Args = --allow-incomplete-classpath --initialize-at-build-time=org.slf4j.Logger,org.slf4j.LoggerFactory,org.slf4j.MDC -J--add-exports=java.management/sun.management=ALL-UNNAMED -H:DynamicProxyConfigurationResources=${.}/proxy.json -H:ReflectionConfigurationResources=${.}/reflect.json -H:ResourceConfigurationResources=${.}/resource.json -Dsys.propspring-native
那么spring-native给我们带来了哪些便利呢?
spring-native提供了诸多注解,帮助我们生成GraalVM所规范的配置。
比如InitializationHint注解可以生成native-image.properties里的--initialize-at-build-time"、"--initialize-at-run-time"。
比如JdkProxyHint注解可以生成proxy-config.json的内容。
比如ResourceHint注解可以生成resource-config.json内容。
比如SerializationHint注解生成serialization-config.json需要的序列化/反序列化内容。
比如TypeHint注解(明细配置可结合FieldHint和MethodHint)生成reflect-config.json需要的哪些字段/方法可被反射调用。
介绍过开胃菜,接下来就是正式的大餐:IoC和AOP。
我们知道spring提供了xml、java代码、java注解等多种配置方式,帮助我们生成bean对象、查找并注入依赖、增强bean。而spring-boot提供了自动配置,开启约定大于配置,让我们无需在每次新建系统时重复配置。
spring-native针对这些功能,结合GraalVM的特点,为我们生成代码,来接好spring/spring-boot的岗位。spring-native会自动为每个存在依赖注入的包生成ContextBootstrapInitializer类,为每个类生成register前缀与类名组合的方法。方法内容大致可表示如下:public final class ContextBootstrapInitializer { public static void register${klazz.simpleName}(DefaultListableBeanFactory beanFactory) { BeanDefinitionRegistrar.of("${beanName}", ${klazz.simpleName}.class) .instanceSupplier((instanceContext) -> { ${klazz.simpleName} bean = new ${klazz.simpleName}(); instanceContext.field("${field.name}", ${field.class.simpleName}.class) .invoke(beanFactory, (attributes) -> { Field ${field.name}Field = ReflectionUtils.findField(${klazz.simpleName}.class, "${field.name}", ${field.class.simpleName}.class); ReflectionUtils.makeAccessible(${field.name}Field); ReflectionUtils.setField(${field.name}Field, bean, attributes.get(0)); }); }) } }
对于spring-boot的autoconfiguration,在满足条件时,同样生成ContextBootstrapInitializer。对于各个jar包下的spring.factories文件,在"org.springframework.aot"下生成StaticSpringFactories,将类型和生成对象方式保存,以供后续使用。
在org.springframework.aot包下还生成了ContextBootstrapInitializer,与应用定义bean的不同之处在于,此类实现了ApplicationContextInitializer。
在SpringApplication构造时,通过生成的SpringFactoriesLoader加载类型及对应的对象(非AOT模式为加载spring.factories,AOT模式为StaticSpringFactories),引起初始化,大致代码如下:public class ContextBootstrapInitializer implements ApplicationContextInitializer { private ImportAwareBeanPostProcessor createImportAwareBeanPostProcessor() { Map mappings = new LinkedHashMap<>(); mappings.put("org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration", "ml.iamwhatiam.baostock.Application"); mappings.put("org.springframework.cache.annotation.ProxyCachingConfiguration", "ml.iamwhatiam.baostock.Application"); return new ImportAwareBeanPostProcessor(mappings); } @Override public void initialize(GenericApplicationContext context) { // infrastructure DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); beanFactory.addBeanPostProcessor(createImportAwareBeanPostProcessor()); BeanDefinitionRegistrar.of("${mainClass.name}", ${mainClass.simpleName}.class) .instanceSupplier(${mainClass.simpleName}::new).register(beanFactory); ${groupId}.${artifactId}.ContextBootstrapInitializer.register${klazz.simpleName}(beanFactory); BeanDefinitionRegistrar.of("org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor", ConfigurationPropertiesBindingPostProcessor.class) .instanceSupplier(ConfigurationPropertiesBindingPostProcessor::new).customize((bd) -> bd.setRole(2)).register(beanFactory); // other EnableAutoConfiguration bean org.springframework.boot.autoconfigure.cache.ContextBootstrapInitializer.registerSimpleCacheConfiguration(beanFactory); } }
当然,并非所有的自动配置类其类型都是公开的("class XXAutoconfiguration",而不是"public class XXAutoConfiguration"),对于这种情况,spring-native为他们在同包名下生成_FactoryProvider类,该静态方法名为类型名,返回该类型。
还有些bean的类型,其构造函数是私有的,spring-native为其生成内部类来创建实例。
至此,当spring-boot的SpringApplication运行时,创建完DefaultBootstrapContext便由"org.springframework.aot"下的ContextBootstrapInitializer进行初始化,此时便会将各种bean及其生成方式注册到spring的上下文。