SpringBoot3.x原生镜像-Native Image实践

前提

之前曾经写过一篇《SpringBoot3.x 原生镜像-Native Image 尝鲜》,当时SpringBoot处于3.0.0-M5版本,功能尚未稳定。这次会基于SpringBoot当前最新的稳定版本3.1.2详细分析Native Image的实践过程。系统或者软件版本清单如下:

组件版本备注
macOS Ventura13.4.1(c)ARM架构
sdkman5.18.2JDK和各类SDK包管理工具
Liberica Native Image Kit23.0.1.r17-nik可以构建Native ImageJDK
SpringBoot3.1.2使用当前(2023-08-20)最新发布版
Maven3.9.0-

安装 sdkman

sdkman是一个轻量级、支持多平台的开源开发工具管理器,可以通过它安装任意主流发行版本(例如OpenJDKKonaGraalVM等等)的任意版本的JDK。通过下面的命令可以轻易安装sdkman:

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version
afdc231d369687f2f5d83f9118213d38.png
spring-boot-native-image-guide-1

可以通过sdk list java查看支持的JDK发行版本:

4775c9033ebc54f727e1c70124942398.png

spring-boot-native-image-guide-2

通过shell命令sdk install java $Identifier就可以安装对应的JDK发行版。例如可以这样安装GraalVM-ce-17:

sdk install java 17.0.8-graalce

通过shell命令sdk uninstall java $Identifier可以卸载对应的JDK发行版。如果安装了多个版本或者多个发行版的JDK,可以通过shell命令sdk default java $Identifier去指定默认使用的JDK版本,例如:

sdk default java 17.0.8-graalce

可以通过shell命令sdk current或者sdk current java查看当前正在使用的SDK或者JDK版本。

0a5717d4e53f569c72ae97b5405bd9e8.png

spring-boot-native-image-guide-3

安装 Liberica NIK

Liberica Native Image Kitbellsoft出品的旨在创建高性能本地二进制(Native Binaries)基于JVM编写的应用的工具包,简称为Liberica NIKLiberica NIK本质就是把OpenJDK和多种其他工具包一起封装起来的JDK发行版,在Native Image功能应用过程,可以简单把它视为OpenJDK + GraalVM的结合体。可以通过sdk list java查看相应的JDK版本:

fd1d1bb15aa00454682230d0ee10e786.png

spring-boot-native-image-guide-4

这里选择JDK-17的版本进行安装:

sdk install java 23.0.1.r17-nik
# 这里最好把此JDK设置为当前系统的默认JDK,否则后面编译镜像时候会提示找不到GraalVM
sdk default java 23.0.1.r17-nik

安装完成后,通过java -version验证一下:

83646d6e7114e71d16c4ae04613e6627.png

spring-boot-native-image-guide-5

编写 SpringBoot 应用

基于Maven新建一个SpringBoot应用,这里已经整理好了一份POM文件,实践过程可以直接用,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.vlts</groupId>
    <artifactId>spring-boot-native-image-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath/>
    </parent>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.version>3.11.0</maven.compiler.version>
        <maven.install.version>3.1.1</maven.install.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-websocket</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.experimental</groupId>
            <artifactId>tomcat-embed-programmatic</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <groupId>org.apache.maven.plugins</groupId>
                <version>${maven.compiler.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-install-plugin</artifactId>
                <version>${maven.install.version}</version>
            </plugin>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>cn.vlts.NativeImageApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

这里把Maven的所有插件都提升到当前()最新版本,原生镜像打包的关键插件是native-maven-plugin,此插件是跟随spring-boot-starter-parent进行版本管理,这里无须指定插件的版本。另外,tomcat-embed-programmatic是一个实验性依赖,可以降低嵌入式Tomcat的内存使用,在生产中应用时候可以暂不启用此特性。接着编写启动类cn.vlts.NativeImageApplication

@SpringBootApplication
@RestController
public class NativeImageApplication {

    public static void main(String[] args) {
        SpringApplication.run(NativeImageApplication.class, args);
    }

    @RequestMapping(path = "/")
    public ResponseEntity<String> index() {
        return ResponseEntity.ok("index");
    }
}

构建、测试与发布

三个操作的Maven命令分别是:

  • 构建:mvn -Pnative native:compile

  • 测试:mvn -PnativeTest test

  • 发布:mvn -Pnative spring-boot:build-image,注意此命令会打包镜像并且发布到Docker的官方仓库中

虽然 native:compile 命令表面意义是编译,但是实际上它就是构建原生镜像的命令

执行构建流程:

mvn -Pnative native:compile -Dmaven.test.skip=true

构建结果如下:

b624e94c317b26c47a2e4b912dffd1d6.png

spring-boot-native-image-guide-6

其中这个不带.jar后缀的就是最终的原生镜像,并且Native Image是不支持跨平台的,它只能在ARM架构的macOS中运行(受限于笔者的编译环境)。可以发现它(见上图中的target/spring-boot-native-image-demo,它是一个二进制执行文件)的体积比executable jar大好几倍。参照SpringBoot的官方文档,经过AOT编译的SpringBoot应用会生成下面的文件:

  • Java源代码

  • 字节码(例如动态代理编译后的产物等)

  • GraalVM识别的提示文件:

    • 资源提示文件(resource-config.json

    • 反射提示文件(reflect-config.json

    • 序列化提示文件(serialization-config.json

    • Java(动态)代理提示文件(proxy-config.json

    • JNI提示文件(jni-config.json

这里的输出非执行包产物基本都在target/spring-aot目录下,其他非Spring或者项目源代码相关的产物输出到graalvm-reachability-metadata目录中。最后可以验证一下产出的Native Image

79781eacf165d08007ae8ab46ec4f1f5.png

spring-boot-native-image-guide-7

可以看到启动速度达到惊人的毫秒级别,如果应用在生产中应该可以全天候近乎无损发布。当然,理论上Native Image性能也会大幅度提升,但是限于篇幅这里暂时不进行性能测试。

小结

鉴于SpringBoot3.x的正式版已经推出一段时间,从文档上看,Native Image使用的技术已经相对成熟,能够用于生产条件。当然,Native Image目前还存在一些局限性会让一些组件完全无法使用或者部分功能受限(参考Spring Boot with GraalVM),希望这些问题或者局限性有一天能够突破让所有JVM应用迎来一次性能飞跃。

(本文完 c-2-d e-a-20230820)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Throwable文摘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值