当提到 "Java 原生编译"(GraalVM Native Image),你想到的是什么?无尽的 JSON 元信息配置文件、反复试错的反射登记、比应用本身还复杂的构建脚本?

Solon 换了一条路。它的 AOT 流水线采用三段式编译,前一阶段的产出自动喂给下一阶段。框架的"克制"哲学——最小化动态代理、用显式配置替代"魔法"——使得原生编译比你想的简单。

三段式流水线

Solon Native 编译分三个阶段,每个阶段都可以独立运行:

阶段 做什么 Maven 命令 JDK 要求
1 标准 Java 编译 mvn clean -DskipTests=true package jdk8+
2 Solon AOT 处理 mvn clean -DskipTests=true -P aot package jdk8+(任意 JDK)
3 GraalVM 原生编译 mvn clean -DskipTests=true -P native native:compile graalvm jdk17+

第二阶段是 Solon 真正干活的地方。

深入第二阶段:Solon AOT

Solon AOT 处理器(SolonAotProcessor,来自 solon-aot 模块)在使用 -P aot profile 时,自动在 mvn package 之后执行。

它在编译时启动你的应用程序,完整走一遍启动流程,捕获框架在运行时需要的所有信息:

  • 代理类预编译——原本需要在运行时通过 ASM 字节码生成的 AOP 动态代理,在构建阶段就编译成真实的 .class 文件
  • 类索引生成——项目类多的时候,这个索引通过跳过类路径扫描来加速启动
  • GraalVM 元信息生成——反射、资源、序列化的元信息自动写入 GraalVM 原生配置格式

关键细节:自 v3.7.2 起,-P aot 可以在任意 JDK(不只是 GraalVM)上运行。-P aot 去掉了 GraalVM 构建工具依赖,让 AOT 成为一个轻量优化过程,即使你还在 JDK 8 上也能用。

当你准备编译完整的原生二进制时,使用 -P native——它会引入 GraalVM 的 native-maven-plugin(需要 GraalVM JDK)。

能得到什么

用 Solon 编译原生可执行文件,换来的是:

  • 毫秒级启动——没有 JVM 预热,没有类加载开销
  • 内存介于 JVM 和 Go 之间——GraalVM 原生镜像去掉了所有未使用的代码
  • 独立二进制文件——不需要 JRE,不需要 java -jar。直接 ./myapp

这些优势对 Serverless 函数、容器化微服务和 CLI 工具尤其有意义。

现实:原生编译的限制

GraalVM 原生镜像有一些硬约束。以下是 Solon 的 AOT 流水线自动处理的内容:

约束 Solon 的处理方式
所有反射必须提前登记 Solon AOT 启动应用、捕获反射使用、生成配置文件
所有资源文件必须提前登记 同上——在 AOT 引导阶段自动收集
不能运行时扫描类路径 改用 ResourceUtil.scanResources()——从预登记的资源索引读取
不能动态编译 改用表达式引擎(SnEL)或脚本工具
不能用 ASM 生成字节码 Solon AOT 在第二阶段预编译代理类

手动补充:RuntimeNativeRegistrar

没有自动系统是完美的。那些在 Solon 托管范围之外使用反射或加载资源的第三方库,需要手动登记。

Solon 提供了 RuntimeNativeRegistrar 接口——一个 @Component bean,让你完全控制登记内容:

@Component
public class NativeRegistrar implements RuntimeNativeRegistrar {
    @Override
    public void register(AppContext ctx, RuntimeNativeMetadata metadata) {
        // 登记未自动检测到的资源文件
        metadata.registerResourceInclude("com/mysql/jdbc/LocalizedErrorMessages.properties");
        
        // 登记需要序列化的类
        metadata.registerSerialization(JsonResult.class);
        
        // 登记需要反射访问的类,指定成员类别
        metadata.registerReflection(BufferedImage.class, 
            MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
            MemberCategory.INVOKE_DECLARED_METHODS);
    }
}

第三方库通常分四类:

  • A:不支持——使用动态编译或字节码操作的框架(如 CGLIB 代理)。原生下无法工作。
  • B:需要较多配置——如 mysql-connector-java 5.x。需要额外的登记工作。
  • C:需要少量配置——如 mysql-connector-java 8.x。一两个资源登记就够了。
  • D:完全支持——库自带 native-image 元信息。

Solon 团队用 nginxWebUI 做了完整的适配案例——一个真实项目,大约花了大半天完成适配。

原生感知代码工具

在编写可能在 JVM 和原生环境都能运行的代码时,有三个工具类:

  • NativeDetector.inNativeImage() —— 检测是否在原生镜像中运行
  • ResourceUtil —— 原生兼容的资源获取
  • ReflectUtil —— 原生兼容的反射工具

它们在 JVM 上委托给标准 Java 反射,在原生镜像中从注册的元数据读取。

快速上手

想自己试试?只需要几步:

  1. 添加依赖——solon-aot(org.noear,由 BOM 管理版本)
  2. 安装 GraalVM——JDK 17、21 或 25;运行 gu install native-image
  3. 单模块项目mvn clean -P native native:compile -DskipTests
  4. 多模块项目:先对所有模块执行 mvn install,然后在主模块执行 -P native native:compile

生成的二进制文件在 target/ 下——不需要 JRE,直接运行。

诚实的局限性

Solon 的 AOT 流水线是务实的,不是万能的:

  1. 首次编译很慢——GraalVM 需要分析整个闭包世界。不是秒级,是分钟级。
  2. 不是所有库都能用——如果依赖用了动态类加载或字节码生成,要么替换它,要么跳过原生编译。
  3. -P aot 对小项目没什么用——对只有 ~10 个类的最小项目,启动提升可以忽略(文档里用 native-example 在 2020 款 MacBook Pro 上验证过)。

三段式编译是 Solon 对 Java 原生编译的务实回答:尽可能自动化,同时诚实地告诉你边界在哪。-P aot profile 可以在任意 JDK 上使用,让你在不投入完整原生编译的前提下获得元信息生成的好处。

如果你之前因为配置复杂而回避 GraalVM,Solon 的流水线移除掉了大部分摩擦。手动补充接口在那里,但大多数时候你不会需要它。


原文地址: https://www.cveoy.top/t/topic/qHj5 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录