当前位置: 首页 > news >正文

SpringBoot应用启动太慢?试试把它编译成Native原生应用

最近我深入研究了 GraalVM Native 技术,尝试将 SpringBoot 应用编译为原生应用,以提升边缘端场景下的运行性能并降低资源占用。虽然网上相关文章不少,但有点千篇一律,在实操过程中仍然踩了不少的“坑”。相信有类似需求的同学并不在少数,希望通过本文的分享,让大家入门不踩坑,一次性成功将 SpringBoot 应用编译为高效的原生二进制文件。O(∩_∩)O

接下来跳过一切不必要的环节,直接上干货,带着大家从零实现。只要按照下面的步骤操作,你一定能一次成功,并会有种“原来如此”的透彻感。如果你想更深入地了解 GraalVM Native,可以阅读我写的另一篇文章《无需JVM!GraalVM打造Windows平台零依赖Java应用》。话不多说,我们直接开始 O(∩_∩)O

一、基于Spring Native编译应用

我们先从一个简单的标准的SpringBoot应用开始,带着大家逐步实现。Easy!Easy!

1、pom.xml 核心内容

<!-- ################## --> <!-- 继承SpringBoot父项目 --> <!-- ################## --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- ### --> <!-- 插件 --> <!-- ### --> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> </plugin> </plugins> </build> <!-- ####### --> <!-- 依赖配置 --> <!-- ####### --> <dependencies> <!-- Spring MVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies>

注:从 SpringBoot 3.x 开始支持 GraalVM Native,请确保版本正确。

2、application.yml

#服务器配置(仅在SpringBoot内有效,外部Tomcat等容器无效) server: port: 80 #服务器端口 servlet: encoding: charset: UTF-8

3、SpringBoot 启动类

import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Spring Boot启动类 * * @Author Tommy * @Date 2026/02/04 */ @SpringBootApplication // 默认是扫描当前包以及子包的所有类 @Slf4j public class BootApplication { public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); log.info("请访问:http://localhost"); } }

4、编写一个测试Controller

import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/home") public String home() { return "Hello Spring Native Based on GraalVM! ___ "; } }

OK!一个简单的标准SpringBoot应用编写完成,相信大家都比较熟悉吧!我们接下来开始打包编译Native喽!

5、通过Maven打包编译

mvn clean -Pnative native:compile

注:native profile 是内置于 SpringBoot 3中的。

输出如下内容表示成功

[INFO] Scanning for projects... [INFO] [INFO] -------------------------< com.zongjin:tycode >------------------------- [INFO] Building tycode 2.10.0 [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- .... [1/8] Initializing... (12.5s @ 0.20GB) Java version: 17.0.18+8-LTS, vendor version: Oracle GraalVM 17.0.18+8.1 Graal compiler: optimization level: 2, target machine: x86-64-v3, PGO: ML-inferred C compiler: cl.exe (microsoft, x64, 19.50.35723) .... ============================================================================== Finished generating 'tycode' in 4m 22s. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 04:44 min [INFO] Finished at: 2026-02-08T08:17:11+08:00 [INFO] ------------------------------------------------------------------------

注:打包过程比较耗时,请大家耐心等待。另外,编译时,请务必关闭 360等安全软件,否则会失败。

6、启动测试

双击启动,观察如图,发现变化了没 O(∩_∩)O

通过以上步骤,就完成了一个SpringBoot应用从零到Native的全部编译过程。是不是So Easy呢!O(∩_∩)O 但你以为到此结束了吗?NO! NO! NO!
好戏才刚刚开始...

二、常见的坑

在实际开发中,将 SpringBoot 应用编译为原生二进制文件往往会遇到不少挑战,尤其是各种看似“无厘头”的报错——有时编译阶段一切顺利,一到运行时却错误频出。更令人头疼的是,部分错误信息本身还带有误导性,排查起来相当耗费精力,让你苦不堪言。接下来,我们就重点剖析几个常见的典型问题 。

剖析方式采用:看日志剖原因破问题

1、Oracle数据库驱动包问题
  • 日志内容

  • 原因分析

起初,看到这个报错日志,猜测问题应该如下:

  • logback 日志框架可能存在问题,或不支持,网上也有资料表明;
  • 不支持的原因,可能是:Object serialization is currently not supported

我就按照这个思路一路排查,跟随着网上博文,一路查,一路错,一路无解。(苦笑。。。)

直接说结论Logback没有问题,且支持Native模式,问题是由 odjbc.jar 引起的。(日志中尽然毫无迹象,太坑啦)

  • 解决办法

ojdbc.jar驱动包从 ojdbc11 版本开始,才支持Native模式,它内置 GraalVM 配置文件,位于META-INF/native-image目录。这些配置文件使得驱动能够与 GraalVM Native Image 工具链兼容,支持将 Java 应用编译为原生可执行文件。

建议

  • 强烈推荐使用ojdbc17
  • 若要使用ojdbc11,请确保版本在23.9.0.25.07以上。
2、SpringMVC Controller 请求报错
  • Controller部分源码
@PostMapping("/generate") public String generate(Requirement requirement, Model model) throws Exception { log.info("代码生成中... " + requirement); // 获取表字段定义元数据 final List<Mapping> mappings = genCodeService.getMappingData(requirement); // 生成各组件源代码 final List<Result> resultList = genCodeService.generateSources(requirement, mappings); model.addAttribute("resultList", resultList); model.addAttribute("clazzName", requirement.getClazzName()); return "result"; }

上述代码在JVM上运行时,一切正常。一般做SpringMVC开发,也是如此Coding。

  • 日志内容

  • 原因分析

此应用编译时一切正常,但运行时,报错上图。也很无厘头,报错的内容,对于排查错误帮助不大。

日志所说事情如下:

  • 自定义类 Requirement 缺少唯一的构造函数 或 存在多个构造函数,没有标注Primary
  • 实际情况,Requirement类是很普通的类,通过 lomobok 注解@Data标记,并不存在日志所说问题(再次陷入郁闷中....)

说结论此问题与类Requirement的源码实现,并无关系。

  • 解决办法

使用注解@ModelAttribute标记Controller的参数,即可解决此问题。因为自定义类,Spring AOT插件,没有进行Native兼容处理。

什么时候需要用注解 @ModelAttribute呢?

  • 并不是所有Controller方法的参数都需要添加此注解;
  • 如果你的参数是自定义类,则需要添加此注解。

上述代码改造后如下:

3、自己的资源文件未被导入
  • 日志内容

  • 原因分析

这个日志提示没有坑,表述很准确。但奇怪的是,为啥这个自定义的配置文件没被打包呢?SpringBoot的yml、templates、css、js等文件,都正常被打包啦!出现了差异化对待,是不是很疑惑呢?

我们先来看下目录结构:

几点说明:

  • 国际化是我们常用的功能,但资源包没被打包,i18N功能异常;
  • SpringBoot 约定的文件都正常被Package,原因在Spring AOT插件,该插件很厉害,会自动分析整个项目,生成若干 native image 配置文件,确保原生编译的正确性;
  • 我们自己添加的文件,并不在SpringBoot的约定中,AOT没有为咱们生成配置,这也就是根因啦!
  • 我们是否可以手动告诉Native打包工具,哪些文件需要被打包呢?答案是肯定,快来看看怎么做吧。

解决办法

  • resource-config.json

额外的资源文件,我们通过这个配置文件告诉 native 工具,它就可以帮我们打包啦!(其实 Spring AOT 插件做的事情,就是自动生成这些配置文件)

配置内容如下:

{ "resources": { "includes": [ { "pattern": "repository/.*" }, { "pattern": "i18n/.*\\.properties$" } ], "bundles": [ { "name": "i18n/language", "locales": ["", "en_US", "ja_JP"] } ] } }

注:更多介绍 请查看官网https://www.graalvm.org/jdk21/reference-manual/native-image/dynamic-features/Resources/

4、如何确认文件是否被打包入二进制文件

这一节并非要提出一个问题,而是想与大家探讨:有没有办法可以确认自己的资源文件是否已被成功打包?这样一来,心里也能更踏实一些。

答案当然是肯定的啦!我们可以借助 Spring 的能力,通过如下代码达到此目的哦!O(∩_∩)O

源码:

@GetMapping("/printsys") @ResponseBody public String printsys() { String lineBreak = "<br/>"; StringBuilder msgBuilder = new StringBuilder("Classpath下面的所有文件与目录"); msgBuilder.append(lineBreak); // 1. 创建解析器 PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver(); // 2. 定义搜索模式:classpath根目录下的所有资源 String pattern = ResourceLoader.CLASSPATH_URL_PREFIX + "/**"; try { // 3. 获取所有匹配的资源 Resource[] resources = patternResolver.getResources(pattern); // 4. 处理资源 for (Resource resource : resources) { msgBuilder.append("资源URL: ").append(resource.getURL()).append(lineBreak); msgBuilder.append("是否存在: ").append(resource.exists()).append(lineBreak); msgBuilder.append("文件名: ").append(resource.getFilename()).append(lineBreak); msgBuilder.append("描述: ").append(resource.getDescription()).append(lineBreak); if (resource.exists() && resource.isFile() && resource.isReadable()) { msgBuilder.append("大小:").append(resource.getInputStream().available()).append(lineBreak); } msgBuilder.append("---------------------------------").append(lineBreak); } } catch (Exception e) { log.error(e.getMessage(), e); msgBuilder.append("发生异常,请查看后台日志。").append(e.getMessage()).append(lineBreak); } return msgBuilder.toString(); }

双击运行二进制应用,访问此REST,输出如下内容,一切一目了然:

到此分析结束!

Enjoy It!

http://www.jsqmd.com/news/358858/

相关文章:

  • 入门指南:基于 CANN 仓库快速理解AI软件栈开发流程
  • AI视角下的 CANN 仓库架构全解析:高效计算的核心
  • 互联网大厂Java求职面试实战:微服务、电商场景与Spring生态详解
  • 用 CANN ops-nn 提升 AI 性能:实操技巧与核心逻辑拆解
  • 优化校园光环境:从照亮空间到专业护眼照明转变
  • 用MonkeyOCR解析复杂PDF
  • CANN 生态新进展:ops-nn 仓库如何赋能大模型训练?
  • USACO历年黄金组真题解析 | 2005年11月
  • 格莱美评审官方认证!吴克群“忠于自我”创作观成国际标杆,他早就该被世界看见!
  • OpenClaw Slack 集成指南
  • 编程大师-技术-算法-leetcode-1472. 设计浏览器历史记录
  • python synonyms库,深度解析
  • 微痕之下,十年追凶——《风过留痕》以痕检视角揭开改编自真实案件的刑侦迷雾
  • PostgreSQL 性能优化:分区表实战
  • python openai库,深度解析
  • PostgreSQL 性能优化:如何安全地终止一个正在执行的大事务?
  • 从好命哥到黑天鹅,黄晓明把东北之旅玩成了喜剧片
  • PostgreSQL性能优化:如何定期清理无用索引以释放磁盘空间(索引膨胀监控)
  • python Flower库,深度解析
  • Python requests 库,深度解析
  • python jieba库,深度解析
  • 第七节:框架版本大升级(CoreMvc10.x + EFCore10.x)
  • C++ 面向控制标记编程(CMOP)到底是什么?一篇讲透这个小众但优雅的范式
  • 完整教程:XILINX SRIOIP核详解、FPGA实现及仿真全流程(Serial RapidIO Gen2 Endpoint v4.1)
  • 探索风力发电MPPT并网模型:策略模块的奇妙世界
  • 思考是用来解决问题和总结经验的,而不是用来制造障碍的:不为打翻的牛奶哭泣底层逻辑是,哭泣仅仅是情绪表达,不是在解决问题,我们应该想的是尽快打扫不要扎到脚
  • USACO历年黄金组真题解析 | 2006年1月
  • 完整教程:【无标题】六边形拓扑量子计算:NP完全问题的统一解决框架
  • 【小程序毕设全套源码+文档】基于Android的陪诊护理系统APP的设计与实现(丰富项目+远程调试+讲解+定制)
  • 手把手撸一个VRPTW求解器(附MATLAB源码)