我把 Solon 打包成了native image,速度快的惊人

Solon 一个高效的应用开发框架:更快、更小、更简单。https://solon.noear.org/

我刚开始对 Solon 感兴趣的原因,就是启动快、包体积小,用了一段时间之后,发现 Solon 使用 GraalVM native iamge 打包有一些问题,我把问题发到 Solon 用户群里,作者告诉我 Solon 的原生编译还 beat 阶段,只做了一部分,问我有没有兴趣,然后我就一边研究 GraalVM naitve image 规范,一边看 Spring 的源码,看看前辈们是怎么实现的,到今天终于在 Solon 上有了阶段性的进展。

图片
图片
启动耗时13ms,内存占用13.5MB(不同机器启动时间会有所差异)

示例源码地址:https://github.com/dudiao/solon-native-example

什么是 Solon AOT ?

AOT 是 Ahead-Of-Time 的简写,指运行前编译,与之对应的是 JIT,即 Just-In-Time,即时(动态)编译,边运行边编译。

Solon AOT 实际上是指 Solon AOT 优化,帮助 GraalVM 更好的将 Solon 应用编译为本机可执行程序(native image),大体思路是构建时,检查应用上下文,找到被使用的类、方法、字段等,并做出相应的决策。做到这些,需要在构建时启动应用,才能获取到上下文 AopContext,Solon AOT 在处理中,可能会生成:

  • Java 类(通常是动态代理)
  • RuntimeNativeMetadata 运行时元数据,包括反射,资源,序列化等
  • Solon 运行时元数据

使用方式

1. 增加solon.aot和solon.apt依赖

<!--solon native start-->
<!--aot 注册native元信息-->
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>solon.aot</artifactId>
</dependency>
<!-- apt 生成代理类 -->
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>solon.proxy.apt</artifactId>
  <scope>provided</scope>
</dependency>
<!--solon native end-->

2. 注册需要的运行时元数据(可选)

比如 User 类需要序列化,可以这样注册:

@Component
public class MyNativeRegistrar implements RuntimeNativeRegistrar {

  @Override
  public void register(AopContext context, RuntimeNativeMetadata nativeMetadata) {
    nativeMetadata.registerSerialization(User.class);
  }
}

实现RuntimeNativeRegistrar接口,且实现类需要是一个solon bean。

3. 生成为本机可执行程序(native image)

环境要求:graalvm 17 & native-image
如果你的项目中使用solon-parent来管理依赖,比如:

<parent>
    <groupId>org.noear</groupId>
    <artifactId>solon-parent</artifactId>
    <version>2.2.13-SNAPSHOT</version>
    <relativePath />
</parent>

<groupId>com.dudiao.solon</groupId>
<artifactId>solon-native-example</artifactId>
<version>1.0</version>

直接使用如下命令打包为 native image

mvn clean native:compile -P native

如果你的项目中没有使用solon-parent,可以在pom.xml中增加:

<profiles>
  <profile>
    <id>native</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.noear</groupId>
          <artifactId>solon-maven-plugin</artifactId>
          <version>${solon.version}</version>
          <executions>
            <execution>
              <id>process-aot</id>
              <goals>
                <goal>process-aot</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.graalvm.buildtools</groupId>
          <artifactId>native-maven-plugin</artifactId>
          <version>${native.version}</version>
          <!-- 使用graalvm提供的可达性元数据,很多第三方库就直接可以构建成可执行文件了 -->
          <configuration>
            <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>

同样使用mvn clean native:compile -P native即可打包为本机可执行程序。

也可以参考 solon native 的示例项目:
https://github.com/dudiao/solon-native-example

实现原理

图片

Solon AOT的总体思路是:

  1. 编译时,通过 apt 生成代理类(之后这部分逻辑会迁移到 solon-maven-plugin 中);
  2. Maven 构建时,增加ProcessAotMojo,用来收集应用的依赖包,通过 java -cp 命令调用SolonAotProcessor
  3. SolonAotProcessor中,会先反射执行应用主类(标记@SolonMain注解),获取到应用上下文;
  4. 注册AopContext中的 BeanWrap(应用中所有的bean)、MethodWrap(方法包装)到运行时元数据中;注册所有插件(PluginEntity)到元数据中;
  5. 用户手动注册到运行时元数据,实现 RuntimeNativeRegistrar 接口;
  6. 生成GraalVM Reachability Metadata(可达性元数据),包含:native-image.properties、resource-config.json、reflect-config.json、serialization-config.json。同时还会生成Solon元数据文件 solon-resource.json,用于在 native 环境中,扫描某个 resource 目录下的资源。

注意事项

通过静态编译构建的二进制程序,虽然有内存占用小,启动速度快的优点,但也有一些局限性,比如不能在运行时获取某个类的所有方法、获取所有 resource 资源。

Solon 正尝试另外一种方式来间接实现:通过在 AOT 阶段生成的元数据文件:reflect-config.jsonsolon-resource.json,运行时读取这两个文件,reflect-config.json包含了类和字段的信息,solon-resource.json中包含了resource目录下的资源信息。

可以通过工具类ReflectUtil获取类上所有字段和方法,工具类ScanUtil扫描路径下的所有资源。

最后

Solon + GraalVM native image 无论是启动速度,还有内存占用,都让我眼前一亮,如果你不了解 Solon,可以试一试。

扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论