Solon AOT & Native:三段式编译,从 Java 到原生可执行文件
当提到 "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 反射,在原生镜像中从注册的元数据读取。
快速上手
想自己试试?只需要几步:
- 添加依赖——
solon-aot(org.noear,由 BOM 管理版本) - 安装 GraalVM——JDK 17、21 或 25;运行
gu install native-image - 单模块项目:
mvn clean -P native native:compile -DskipTests - 多模块项目:先对所有模块执行
mvn install,然后在主模块执行-P native native:compile
生成的二进制文件在 target/ 下——不需要 JRE,直接运行。
诚实的局限性
Solon 的 AOT 流水线是务实的,不是万能的:
- 首次编译很慢——GraalVM 需要分析整个闭包世界。不是秒级,是分钟级。
- 不是所有库都能用——如果依赖用了动态类加载或字节码生成,要么替换它,要么跳过原生编译。
-P aot对小项目没什么用——对只有 ~10 个类的最小项目,启动提升可以忽略(文档里用 native-example 在 2020 款 MacBook Pro 上验证过)。
三段式编译是 Solon 对 Java 原生编译的务实回答:尽可能自动化,同时诚实地告诉你边界在哪。-P aot profile 可以在任意 JDK 上使用,让你在不投入完整原生编译的前提下获得元信息生成的好处。
如果你之前因为配置复杂而回避 GraalVM,Solon 的流水线移除掉了大部分摩擦。手动补充接口在那里,但大多数时候你不会需要它。

浙公网安备 33010602011771号