如果你搜到这篇文章,大概率和我面对的是同一件事:yudao 这种基于 Spring Boot 的后台脚手架,平时用 java -jar 跑得好好的,但一旦到了“服务要批量部署”“容器要快速扩容”“冷启动不能等太久”这类场景,传统 JVM 应用那几秒到十几秒的启动时间就变得很扎眼。Native 打包,也就是把 yudao 编译成一个原生可执行文件,是我最近在调的一条路。
先说结论:yudao 不是不能做 Native 打包,但比打包一个纯粹的 Spring Boot Demo 要麻烦不少。麻烦主要来自 yudao 的模块划分、MyBatis Plus 的 XML Mapper、Redis 序列化、验证码字体资源这些“隐式依赖”。真正的难点不在编译,而在把运行时需要的反射、资源、代理信息全部提前交代给 GraalVM。
这篇文章把我自己实际跑通的方案、用到的配置、关键代码,以及踩坑之后完整的问题解决链路写清楚。目标很明确:让你拿到一篇能照着操作的东西,而不是一篇只讲概念的文章。
1. 概念对齐:这个 Native 指的是 GraalVM,不是移动端原生
1.1 为什么先要对齐语义
“Native 打包”这个词在两拨人嘴里完全是两回事。做移动端的人一说 Native,先想到 React Native、uni-app 原生打包、Android WebView 壳;做 Java 后端的人说 Native,通常指 GraalVM Native Image。yudao 是一个 Java 后端脚手架,所以这里的 Native 严格按照 GraalVM 的方向理解。
GraalVM Native Image 干的事情很简单:在构建阶段对你的应用做静态分析,把 JVM 字节码提前编译成机器码,最终产出一个不依赖 JDK 就能运行的原生可执行文件。Spring 应用跑在这个文件里,启动过程不再需要类加载、字节码解释、JIT 预热,所以启动时间能压到几百毫秒到一秒多。
这个思路和移动端打包没有任何关系,大家不要在搜索时被热词带偏。yudao 的前端部分如果要打 App 壳,那是另一套流程,跟标题里的 Native 打包不是一个东西。
1.2 为什么值得让 yudao 吃一次亏
yudao 是典型的“全家桶式”后台项目,模块多、功能全、监控和基础设施模块都有。这类项目放在 JVM 上运行没有任何问题,问题出在三个指标上:
| 指标 | 传统 java -jar | Native 可执行文件 |
|---|---|---|
| 冷启动到可服务 | 我这里是 6 到 9 秒 | 实测 1 秒上下 |
| 稳定运行 RSS 内存 | 跑起来后 800MB 往上 | 260MB 到 350MB 之间 |
| 产物大小 | Fat Jar 大约 170MB | 原生可执行文件约 80 到 100MB |
| 弹性扩容 | 加上预热,Pod 就绪很慢 | 启动快,便于快速扩容 |
| 构建时长 | 分钟级 | 首次十几分钟,需配置缓存 |
如果你的 yudao 只跑一两个内部后台节点,第三列的优势并不明显,Native 的构建问题反而会拖慢开发。但如果你要把 yudao 服务部署成十几个节点,或者要面对大量周期性任务、按量拉起服务,启动速度和内存占用就是实打实的成本。为了这个收益,值得把打包链路的问题梳理一次。
1.3 哪些模块适合先做 Native
yudao 这种工程一般拆成 yudao-server、yudao-module-system、yudao-module-infra,以及框架层的一堆 starter。 Native 化建议从 yudao-server 的 API 服务端开始,其他模块作为 Maven 依赖一起参与编译。
如果你只需要把某个模块或某个调度任务单独打成原生可执行文件,原理一样,只是入口类不同。我的经验是先让 yudao-server 整体跑通,再回头去做精简版本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 前置准备:GraalVM 环境与 Spring Boot 版本改造
2.1 GraalVM 版本选择和安装
Native Image 需要一个带 native-image 组件的 GraalVM。我当前用的是 GraalVM for JDK 17 的较新版本,实际版本号不关键,关键是别用一个很老的 GraalVM 去配新版本 Spring Boot,否则会出现一堆“找不到 native-image”或者编译参数不认的错误。
安装步骤是很基础的:
bash复制# 以 macOS 或 Linux 为例
sdk install java 17.0.12-graalce
sdk use java 17.0.12-graalce
# 安装 native-image 组件
gu install native-image
验证环境:
bash复制java -version
native-image --version
另外确认 JAVA_HOME 指向的是 GraalVM,而不是系统里原来的 JDK。这一步看起来简单,但我在实际项目里见过不止一次:用 IDES 跑 Maven 时识别的是 JDK 17,命令行里却是另一个 JDK,最后 native 插件报错,排查半天发现是环境变量串了。
2.2 yudao 的 Spring Boot 版本决定了可行路径
这是最关键的一个前提。Spring Boot 对 GraalVM Native Image 的支持分水岭是 3.0,因为从 3.0 开始 Spring 官方把 AOT 引擎内置,原来的 Spring Native 实验项目停止演进。
如果你的 yudao 还是基于 Spring Boot 2.7 的分支,做 Native 需要引入 Spring Native 那一套实验性依赖,配置复杂而且很多组件不兼容。我建议直接基于 Spring Boot 3.2 以上的 yudao 版本来做。yudao 本身升 Boot 3 的分支已经比较成熟,主要是切换 Jakarta 命名空间、调整部分配置项,整体改动量可控。
我自己在文案版本上做的事主要有三件:
- 把 Spring Boot 版本统一到 3.x,确保依赖树里没有 Spring Boot 2.x 的残留。
- 确认
javax.*包全部换成了jakarta.*,MyBatis Plus、Sa-Token、Redisson 这些关键依赖都要有对应 Boot 3 的版本。 - 检查自定义 starter 里有没有直接 new ClassPathResource、Class.forName、动态代理之类代码,这些都可能是 Native 打包的定时炸弹。
Spring Boot 3.x 并不是“改完就能直接 native:compile”。它只是把官方支持打通了,具体到 yudao 这种业务复杂的项目,还得靠自己的 RuntimeHints 补齐信息。
2.3 提前清理不符合 AOT 条件的代码
在开始打包之前,建议在 yudao 全局搜一下这些模式:
text复制Class.forName
new URLClassLoader
Thread.currentThread().getContextClassLoader()
ServiceLoader.load
反射调用某个未知
