当前位置: 首页 > 图灵资讯 > 技术篇> Spring6.0从入门到精通-Spring AOT的源码实现

Spring6.0从入门到精通-Spring AOT的源码实现

来源:图灵教育
时间:2024-01-17 11:08:52
 

Spring AOT的源码实现

流程图:https://www.processon.com/view/link/63edeea8440e433d3d6a88b2

 

SpringBoot 3.0插件实现原理

上面的SpringBoot3.0实战过程中,我们在利用image-native编译的时候,target目录下会生成一个spring-aot文件夹:

 

这个spring-aot文件夹是编译的时候spring boot3.0的插件生成的,resources/META-INF/native-image文件夹中的存放的就是graalvm的配置文件。

 

当我们执行mvn -Pnative native:compile时,实际上执行的是插件native-maven-plugin的逻辑。

我们可以执行mvn help:describe -Dplugin=org.graalvm.buildtools:native-maven-plugin -Ddetail

来查看这个插件的详细信息。

 

发现native:compile命令对应的实现类为NativeCompileMojo,并且会先执行package这个命令,从而会执行process-aot命令,因为spring-boot-maven-plugin插件中有如下配置:

 

我们可以执行mvn help:describe -Dplugin=org.springframework.boot:spring-boot-maven-plugin -Ddetail

 

发现对应的phase为:prepare-package,所以会在打包之前执行ProcessAotMojo。

 

所以,我们在运行mvn -Pnative native:compile时,会先编译我们自己的java代码,然后执行executeAot()方法(会生成一些Java文件并编译成class文件,以及GraalVM的配置文件),然后才执行利用GraalVM打包出二进制可执行文件。

 

对应的源码实现:

 

maven插件在编译的时候,就会调用到executeAot()这个方法,这个方法会:

  1. 先执行org.springframework.boot.SpringApplicationAotProcessor的main方法
  2. 从而执行SpringApplicationAotProcessor的process()
  3. 从而执行ContextAotProcessor的doProcess(),从而会生成一些java类并放在spring-aot/main/sources目录下,详情看后文
  4. 然后把生成在spring-aot/main/sources目录下的Java类进行编译,并把对应class文件放在项目的编译目录下target/classes
  5. 然后把spring-aot/main/resources目录下的graalvm配置文件复制到target/classes
  6. 然后把spring-aot/main/classes目录下生成的class文件复制到target/classes

 

Spring AOT核心原理

以下只是一些关键源码,详细内容请看直播视频。

 

 

prepareApplicationContext会直接启动我们的SpringBoot,并在触发contextLoaded事件后,返回所创建的Spring对象,注意此时还没有扫描bean

 

protected ClassName performAotProcessing(GenericApplicationContext applicationContext) {
	FileSystemGeneratedFiles generatedFiles = createFileSystemGeneratedFiles();

	DefaultGenerationContext generationContext = new DefaultGenerationContext(createClassNameGenerator(), generatedFiles);
	
	ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();

	// 会进行扫描,并且根据扫描得到的BeanDefinition生成对应的Xx_BeanDefinitions.java文件
	// 并返回com.zhouyu.MyApplication__ApplicationContextInitializer
	ClassName generatedInitializerClassName = generator.processAheadOfTime(applicationContext, generationContext);

	// 因为后续要通过反射调用com.zhouyu.MyApplication__ApplicationContextInitializer的构造方法
    // 所以将相关信息添加到reflect-config.json对应的RuntimeHints中去
	registerEntryPointHint(generationContext, generatedInitializerClassName);

	// 生成source目录下的Java文件
	generationContext.writeGeneratedContent();

	// 将RuntimeHints中的内容写入resource目录下的Graalvm的各个配置文件中
	writeHints(generationContext.getRuntimeHints());
	writeNativeImageProperties(getDefaultNativeImageArguments(getApplicationClass().getName()));

	return generatedInitializerClassName;
}

 

 

public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
									GenerationContext generationContext) {
	return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {
		
		// 会进行扫描,并找到beanType是代理类的请求,把代理类信息设置到RuntimeHints中
		applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());

		// 拿出Bean工厂,扫描得到的BeanDefinition对象在里面
		DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
		
		ApplicationContextInitializationCodeGenerator codeGenerator =
			new ApplicationContextInitializationCodeGenerator(generationContext);
		
		// 核心
		new BeanFactoryInitializationAotContributions(beanFactory).applyTo(generationContext, codeGenerator);
		
		return codeGenerator.getGeneratedClass().getName();
	});
}

 

BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory) {
	// 把aot.factories文件的加载器以及BeanFactory,封装成为一个Loader对象,然后传入
	this(beanFactory, AotServices.factoriesAndBeans(beanFactory));
}

 

BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory,
										  AotServices.Loader loader) {

	// getProcessors()中会从aot.factories以及beanfactory中拿出BeanFactoryInitializationAotProcessor类型的Bean对象
	// 同时还会添加一个RuntimeHintsBeanFactoryInitializationAotProcessor
	this.contributions = getContributions(beanFactory, getProcessors(loader));
}

 

private List<BeanFactoryInitializationAotContribution> getContributions(
	DefaultListableBeanFactory beanFactory,
	List<BeanFactoryInitializationAotProcessor> processors) {
	
	List<BeanFactoryInitializationAotContribution> contributions = new ArrayList<>();

	// 逐个调用BeanFactoryInitializationAotProcessor的processAheadOfTime()开始处理
	for (BeanFactoryInitializationAotProcessor processor : processors) {
		BeanFactoryInitializationAotContribution contribution = processor.processAheadOfTime(beanFactory);
		if (contribution != null) {
			contributions.add(contribution);
		}
	}
	return Collections.unmodifiableList(contributions);
}

 

总结一下,在SpringBoot项目编译时,最终会通过BeanFactoryInitializationAotProcessor来生成Java文件,或者设置RuntimeHints,后续会把写入Java文件到磁盘,将RuntimeHints中的内容写入GraalVM的配置文件,再后面会编译Java文件,再后面就会基于生成出来的GraalVM配置文件打包出二进制可执行文件了。

 

所以我们要看Java文件怎么生成的,RuntimeHints如何收集的就看具体的BeanFactoryInitializationAotProcessor就行了。

 

比如:

  1. 有一个BeanRegistrationsAotProcessor,它就会负责生成Xx_BeanDefinition.java以及Xx__ApplicationContextInitializer.java、Xx__BeanFactoryRegistrations.java中的内容
  2. 还有一个RuntimeHintsBeanFactoryInitializationAotProcessor,它负责从aot.factories文件以及BeanFactory中获取RuntimeHintsRegistrar类型的对象,以及会找到@ImportRuntimeHints所导入的RuntimeHintsRegistrar对象,最终就是从这些RuntimeHintsRegistrar中设置RuntimeHints。

 

Spring Boot3.0启动流程

在run()方法中,SpringBoot会创建一个Spring容器,但是SpringBoot3.0中创建容器逻辑为:

private ConfigurableApplicationContext createContext() {
	if (!AotDetector.useGeneratedArtifacts()) {
		return new AnnotationConfigServletWebServerApplicationContext();
	}
	return new ServletWebServerApplicationContext();
}

如果没有使用AOT,那么就会创建AnnotationConfigServletWebServerApplicationContext,它里面会添加ConfigurationClassPostProcessor,从而会解析配置类,从而会扫描。

 

而如果使用了AOT,则会创建ServletWebServerApplicationContext,它就是一个空容器,它里面没有ConfigurationClassPostProcessor,所以后续不会触发扫描了。

 

创建完容器后,就会找到MyApplication__ApplicationContextInitializer,开始向容器中注册BeanDefinition。

 

后续就是创建Bean对象了。