springboot3 aot 编译过程分析
首先在pom中引入spring-boot-maven-plugin及native-maven-plugin
1.spring-boot-maven-plugin的process-aot过程
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<!--spring-boot-maven-plugin插件仅支持package=jar模式-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<!--必须指定入口类-->
<mainClass>com.opendv.BizProviderApplication</mainClass>
</configuration>
<executions>
<execution>
<id>process-aot</id>
<goals>
<!--process-aot阶段阶段由ProcessAotMojo#executeAot处理-->
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.2</version>
<configuration>
<quickBuild>true</quickBuild>
<buildArgs>
<buildArg>-Db</buildArg>
<buildArg>--enable-url-protocols=http,https</buildArg>
</buildArgs>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
</configuration>
<executions>
<execution>
<id>add-reachability-metadata</id>
<goals>
<goal>add-reachability-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
可以直接对maven进行debug调试 首先在idea中新建一个maven执行
上面是maven构建命令的debug模式 说明:
- 可以直接断点到ProcessAotMojo类,即spring-boot-maven-plugin的执行入口类
- 但是它还是无法debug到SpringApplicationAotProcessor类
- 通常情况下,我们都希望能够debug到自定义处理器上面,从ProcessAotMojo的源码中可以看到SpringApplicationAotProcessor是由JavaProcessExecutor去执行main方法,另外开辟一个子进程进行处理,已经跨进程就无法进行debug,它交给ProcessBuilder去启动一个子进程,执行相关的java命令
- 但是这个入口类,需要几个必要的参数,调用SpringApplicationAotProcessor.main方法分别对应:( applicationName,sourceOutput, resourceOutput ,classOutput ,groupId, artifactId, originalArgs... )
因此可以手写一个main方法进行调试
```
public class AotAgentMain {
public static void main(String[] args) throws Exception {
args = new String[]{
"com.opendv.BizProviderApplication",
"/Users/robin/new_open_project/native-demo/biz-provider/target/spring-aot/main/sources",
"/Users/robin/new_open_project/native-demo/biz-provider/target/spring-aot/main/resources",
"/Users/robin/new_open_project/native-demo/biz-provider/target/spring-aot/main/classes",
"com.opendv.graalvm",
"biz-provider"};
SpringApplicationAotProcessor.main(args);
}
}
```
public class SpringApplicationAotProcessor extends ContextAotProcessor {
public static void main(String[] args) throws Exception {
int requiredArgs = 6;
Assert.isTrue(args.length >= requiredArgs, () -> "Usage: " + SpringApplicationAotProcessor.class.getName()
+ " <applicationName> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId> <originalArgs...>");
Class<?> application = Class.forName(args[0]);
Settings settings = Settings.builder()
.sourceOutput(Paths.get(args[1]))
.resourceOutput(Paths.get(args[2]))
.classOutput(Paths.get(args[3]))
.groupId((StringUtils.hasText(args[4])) ? args[4] : "unspecified")
.artifactId(args[5])
.build();
String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length) : new String[0];
new SpringApplicationAotProcessor(application, settings, applicationArgs).process();
}
}
SpringApplicationAotProcessor#process():
@Override
protected ClassName doProcess() {
deleteExistingOutput();
//@1,调用入口主类方法
GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass());
return performAotProcessing(applicationContext);
}
protected ClassName performAotProcessing(GenericApplicationContext applicationContext) {
//创建资源生成目录
FileSystemGeneratedFiles generatedFiles = createFileSystemGeneratedFiles();
DefaultGenerationContext generationContext = new DefaultGenerationContext( createClassNameGenerator(), generatedFiles);
//创建aot生成器
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
//aot生成器执行回调,加载所有的BeanFactoryInitializationAotProcessor类,最后添加一个RuntimeHintsBeanFactoryInitializationAotProcessor
ClassName generatedInitializerClassName = generator.processAheadOfTime(applicationContext, generationContext);
registerEntryPointHint(generationContext, generatedInitializerClassName);
generationContext.writeGeneratedContent();
writeHints(generationContext.getRuntimeHints());
writeNativeImageProperties(getDefaultNativeImageArguments(getApplicationClass().getName()));
return generatedInitializerClassName;
}
//创建类名生成器
protected ClassNameGenerator createClassNameGenerator() {
return new ClassNameGenerator(ClassName.get(getApplicationClass()));
}
1.native-maven-plugin的add-reachability-metadata过程
native-maven-plugin的add-reachability-metadata阶段是做什么的?
在使用 GraalVM 构建原生镜像时,采用提前编译,这要求在编译时就确定所有会被使用到的类、方法和字段。而 Java 程序中存在很多动态特性,比如反射、动态代理等,这些在编译时很难被 GraalVM 静态分析到。
可达性元数据就是为了解决这个问题而引入的,它告诉 GraalVM 在运行时哪些类、方法和字段是可能会被使用到的,从而确保原生镜像能够正常运行
add-reachability-metadata功能作用:
会扫描项目的依赖,找出这些库提供的可达性元数据文件(通常是 reflect-config.json、resource-config.json、proxy-config.json 等),并将它们合并到项目的元数据中。
下面来 执行mvn clean package -P native调试命令
见AbstractNativeMojo#execute源码
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
//配置元数据仓库地址:
configureMetadataRepository();
//遍历所有的依赖包,如下图2所示
project.getArtifacts().stream().filter(this::isInScope)
.forEach(dependency -> maybeAddDependencyMetadata(dependency, null));
if (isMetadataRepositoryEnabled() && !metadataRepositoryConfigurations.isEmpty()) {
Path destination = outputDirectory.toPath();
try {
DirectoryConfiguration.copy(metadataRepositoryConfigurations, destination);
} catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
}
}
configureMetadataRepository方法的主要作用:
- 发送一个CollectRequest请求,从maven仓库去拉取当前native-maven-plugin版本相对应的元数据(当前版本所支持所有三方组件的json文件)到本地仓库
- 并返回本地仓库路径,如本机如下图3所示
- 在当前项目下面创建一个临时目录,将元数据仓库zip数据,copy到当前目当下面
下载zip文件到仓库目录下面
maybeAddDependencyMetadata:
遍历所有的依赖包看看匹配是否命中仓库里面的元数据 如tomcat-embed-core依赖如下图5,所示将命中的元数据20条,copy到classes目录下面
最终结果是classess/META-INF/native-image是由native-maven-plugin来生成的,主要是从元数据仓库匹配而来
当然 spring-aot目录是由#executeAot是spring-boot-maven-plugin的process-aot阶段所生成的。
总结
aot 处理阶段大部分都由maven插件来完成,也可以在spring的层面去写一些自定义处理器,下次再分析这个问题