springboot3 aot 编译过程分析

199 阅读4分钟

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执行

image.png

上面是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调试命令

image.png

见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到当前目当下面

image.png

下载zip文件到仓库目录下面 image.png

maybeAddDependencyMetadata:

遍历所有的依赖包看看匹配是否命中仓库里面的元数据 如tomcat-embed-core依赖如下图5,所示将命中的元数据20条,copy到classes目录下面

image.png 最终结果是classess/META-INF/native-image是由native-maven-plugin来生成的,主要是从元数据仓库匹配而来

image.png

当然 spring-aot目录是由#executeAot是spring-boot-maven-plugin的process-aot阶段所生成的。

总结

aot 处理阶段大部分都由maven插件来完成,也可以在spring的层面去写一些自定义处理器,下次再分析这个问题