SpringBoot3 Graalvm Native-Image尝鲜

本文介绍了如何在Windows环境下,使用GraalVM 22.3.1和SpringBoot 3.0.2进行native-image构建。详细阐述了从JDK8+SpringBoot2.x到JDK17+SpringBoot3.x的迁移过程,以及GraalVM的安装、配置,包括设置环境变量、安装native-image工具。文章还涉及到Visual Studio Build Tools 2022的安装与验证,以及解决native-maven-plugin插件在Windows上的路径问题。

简介:GraalVM 是一个跨语言的通用虚拟机,不仅支持了 Java、Scala、Groovy、Kotlin 等基于 JVM 的语言,以及 C、C++ 等基于 LLVM 的语言,还支持其它像 JavaScript、Ruby、Rust、Python 和 R 语言等。它消除了编程语言之间的隔离。

GraalVM EE <==== 内置 Oarcle JDK 版本

Oracle GraalVM 企业版是Oralce Java SE 订阅的一部分,无需额外付费,但众多周知除了JDK8u202及以下版本和JDK17版本不收费,Oracle JDK的其他版本是收费的。

GraalVM CE <==== 内置 Open JDK 版本

社区版,免费

Spring支持Graalvm native-image 功能最低要求 springboot3.x ,而支持Springboot3.x功能的最低要求jdk17+ ,所以Graalvm native-image实践的难点在于 JDK8 + springboot2.x + JavaEE 向 JDK17 + springboot3.x + Jakarta EE的项目迁移。

1、参考文档

Springboot3.0.2参考文档

https://docs.spring.io/spring-boot/docs/3.0.2/reference/pdf/spring-boot-reference.pdf

https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/

Springboot3.x环境要求

https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.system-requirements

Graalvm22.x native-image参考文档

https://www.graalvm.org/latest/reference-manual/native-image/

Quarkus 集成native-image参考文档(仅供参考)

https://quarkus.io/guides/building-native-image#producing-a-native-executable

Graalvm最新版官网下载地址

https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-22.3.1

微软VS工具官网下载地址

https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/release-history

https://learn.microsoft.com/zh-cn/visualstudio/releases/2019/history#installing-an-earlier-release

Maven历史版本下载地址

https://archive.apache.org/dist/maven/maven-3/

IDEA 历史版本下载地址

https://www.jetbrains.com/idea/download/other.html

根据以上官方文档的参考,我们选择以下环境

Win10 21H2

Graalvm Community 22.3.1(集成JDK17.06)

Maven3.8.1

IDEA Community 2022.2.4

(兼容JDK8的最后一个版本,且支持JDK17和Springboot3,默认Maven3.8.1)

VSBuildTools Community 2022

(官网要求VisualStudio2017 和 VsBuildTools2017以上版本,个人实践发现只用VSBuildTools工具也能满足需求)

2、环境搭建介质准备

graalvm-ce-java17-windows-amd64-22.3.1.zip

native-image-installable-svm-java17-windows-amd64-22.3.1.jar

vs_BuildTools.exe

apache-maven-3.8.6.zip

ideaIC-2022.2.4.exe

3、Graalvm和Maven安装

graalvm-ce-java17-windows-amd64-22.3.1.zip

解压,配置环境变量

GRAALVM_HOME= D:\ProgramFiles\Java\graalvm-ce-java17-22.3.1

JAVA_HOME= %GRAALVM_HOME%

PATH= %JAVA_HOME%\bin

查看当前Graalvm版本

java -vesrion

gu --version

离线安装native-image工具

gu install -L native-image-installable-svm-java17-windows-amd64-22.3.1.jar

查看安装结果

gu list

apache-maven-3.8.6.zip解压安装配置环境变量

M2_HOME= D:\ProgramFiles\Java\apache-maven-3.8.6

PATH=%M2_HOME%\bin;

mvn -v 查看当前版本

4、VsBuildTool安装

注意上述标记的勾选项部分!!!

(1)确认vcvars64.bat文件是否存在

C:\ProgramFiles (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat

对应VS2022控制台快捷方式

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\VisualStudio 2022\Visual Studio Tools\VC\ x64 Native Tools Command Prompt for VS 2022

(2)确认以下环境变量目录是否存在,并配置对应环境变量

LIB环境变量

C:\ProgramFiles (x86)\Windows Kits\10\Lib\10.0.20348.0\um\x64;

C:\ProgramFiles (x86)\Windows Kits\10\Lib\10.0.20348.0\ucrt\x64;

C:\ProgramFiles (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.34.31933\lib\x64;

INCLUDE环境变量

C:\ProgramFiles (x86)\Windows Kits\10\Include\10.0.20348.0\ucrt;

C:\ProgramFiles (x86)\Windows Kits\10\Include\10.0.20348.0\um;

C:\ProgramFiles (x86)\Windows Kits\10\Include\10.0.20348.0\shared;

C:\ProgramFiles (x86)\Microsoft VisualStudio\2022\BuildTools\VC\Tools\MSVC\14.34.31933\include;

PATH环境变量

C:\ProgramFiles (x86)\Microsoft VisualStudio\2022\BuildTools\VC\Tools\MSVC\14.34.31933\bin\HostX64\x64;

如果上述校验失败,则说明VSbuildTool环境安装时选的组件有缺失,建议完全卸载后重新安装,完全卸载包含正常卸载+删除对应下载目录或缓存目录+清理注册表重启。

5、验证VS环境和native-image工具

创建D:\\test 项目工程目录(注意native-image是基于项目工程目录的,直接D盘下创建HelloWorld.java文件进行下述操作,会将整个D盘作为项目工程目录导致可能发生下图错误)

创建D:\\test\\HelloWorld.java 文件,文件内容如下

publicclass HelloWorld {

public static void main(String[] args) {

System.out.println("Hello, NativeWorld!");

}

}

进入项目工程目录下

执行

javac HelloWorld.java

再执行

native-imageHelloWorld

生成可执行文件如上图,此操作证明了你的VS和Graalvmnative-image环境是正常的

6、IDEA创建springboo3项目并集成native-image

创建完毕后我们发现默认的native-maven-plugin插件版本为0.9.19版本,此版本创建exe可执行文件对应的命令为 mvn -Pnative native:compile

CMD控制台执行

call "C:\ProgramFiles (x86)\Microsoft VisualStudio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"

或者打开控制台

x64 NativeTools Command Prompt for VS 2022

根据官方文档没有执行vcvars64.bat直接使用普通的控制台执行相关命令会导致编译路径异常

切换到当前项目目录,由于此处是使用0.9.19版本native-maven-plugin插件,我们执行以下命令

mvnw -Pnative native:compile

发现报错如下

[INFO] Executing:D:\graalvm-ce-java17-22.3.1\bin\native-image.cmd@target\tmp\native-image-12396827555653810932.args

Exceptionin thread "main" java.nio.file.InvalidPathException: Illegal char< > at index 3: D:\ IdeaProjects\ demo\ target\ classes

根据错误提示我们查看并修改

D:\springboot-native-demo\target\tmp\native-image-7282593071674994904.args

文件将里面的所有\\路径全局替换为/路径符号。

然后我们手动执行刚才报错的命令

native-image @target\tmp\native-image-7282593071674994904.args

(此处我们也可以将args文件中的参数粘贴出来作为native-image 后面的参数,不需要替换路径,只需要将相关参数合并成单行命令执行也能成功,说明native-image通过vcvars64.bat脚本执行后是支持window的路径\\格式的

等价于

执行完毕结果

发现成功了,个人猜测这可能是0.9.19版本native-maven-plugin插件的BUG?可能只在window环境发生,因为window环境的路径分隔符和linux不一致,同时我们可以native-image –help查看上述命令的说明,native-maven-plugin插件源码地址如下

https://github.com/graalvm/native-build-tools 查看native-maven-plugin插件源码

native-build-tools\common\utils\src\main\java\org\graalvm\buildtools\utils\NativeImageUtils.java

public staticList<String> convertToArgsFile(List<String> cliArgs, Path outputDir,Path projectDir)

window环境下解决该办法的方法,复制如下定制修改的类NativeImageUtils.java

package org.graalvm.buildtools.utils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * <dependency>
 * <groupId>org.graalvm.buildtools</groupId>
 * <artifactId>utils</artifactId>
 * <version>0.9.19</version>
 * </dependency>
 * Overlay rewriting  org.graalvm.buildtools.utils.NativeImageUtils
 *
 * @author zhouyanpeng
 */
public class NativeImageUtils {

    //javac -Xlint:unchecked NativeImageUtils.java    ==> Find unchecked Code line And Remove Chinese Comment
    //javac NativeImageUtils.java
    //Replace class File ==> /mvn_repo/org/graalvm/buildtools/utils/0.9.19/utils-0.9.19.jar/org/graalvm/buildtools/utils/NativeImageUtils.class

    private static final Pattern requiredVersionPattern = Pattern.compile("^([0-9]+)(?:\\.([0-9]+)?)?(?:\\.([0-9]+)?)?$");
    private static final Pattern graalvmVersionPattern = Pattern.compile("^GraalVM ([0-9]+)\\.([0-9]+)\\.([0-9]+).*");

    public NativeImageUtils() {
    }

    public static void maybeCreateConfigureUtilSymlink(File configureUtilFile, Path nativeImageExecutablePath) {
        if (!configureUtilFile.exists()) {
            Path target = configureUtilFile.toPath();
            Path source = nativeImageExecutablePath.getParent().getParent().resolve("lib/svm/bin/" + nativeImageConfigureFileName());
            if (Files.exists(source, new LinkOption[0])) {
                try {
                    Files.createLink(target, source);
                } catch (IOException var5) {
                }
            }
        }

    }


    public static String nativeImageConfigureFileName() {
        //OrgCode ==>  return "native-image-configure" + SharedConstants.GRAALVM_EXE_EXTENSION;
        return "native-image-configure" + (System.getProperty("os.name", "unknown").contains("Windows") ? ".cmd" : "");
    }

    public static List<String> convertToArgsFile(List<String> cliArgs, Path outputDir) {
        return convertToArgsFile(cliArgs, outputDir, Paths.get(""));
    }

    /**
     * 此处有Window环境下的定制修改
     *
     * @param cliArgs
     * @param outputDir
     * @param projectDir
     * @returnjava
     */
    public static List<String> convertToArgsFile(List<String> cliArgs, Path outputDir, Path projectDir) {
        try {
            boolean ignored = outputDir.toFile().mkdirs();
            File tmpFile = Files.createTempFile(outputDir, "native-image-", ".args").toFile();
            //Change NativeImageUtils::escapeArg
            cliArgs = cliArgs.stream().map(NativeImageUtils::escapeArg).collect(Collectors.toList());
            Files.write(tmpFile.toPath(), cliArgs, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
            Path resultingPath = tmpFile.toPath().toAbsolutePath();
            if (projectDir != null) {
                resultingPath = projectDir.toAbsolutePath().relativize(resultingPath);
            }
            return Collections.singletonList("@" + resultingPath);
        } catch (IOException var6) {
            return Collections.unmodifiableList(cliArgs);
        }
    }

    public static String escapeArg(String arg) {
        if (!arg.startsWith("\\Q") || !arg.endsWith("\\E")) {
            arg = arg.replace("\\", "\\\\");
            if (arg.contains(" ")) {
                arg = "\"" + arg + "\"";
            }
        }
        //Add This Code To Support Windows Env
        if (System.getProperty("os.name").contains("Windows")) {
            arg = arg.replace("\\\\", "/");
        }
        return arg;
    }

    public static void checkVersion(String requiredVersion, String versionToCheck) {
        Matcher requiredMatcher = requiredVersionPattern.matcher(requiredVersion);
        if (!requiredMatcher.matches()) {
            throw new IllegalArgumentException("Invalid version " + requiredVersion + ", should be for example \"22\", \"22.3\" or \"22.3.0\".");
        } else {
            Matcher checkedMatcher = graalvmVersionPattern.matcher(versionToCheck.trim());
            if (!checkedMatcher.matches()) {
                throw new IllegalArgumentException("Version to check '" + versionToCheck + "' can't be parsed.");
            } else {
                int requiredMajor = Integer.parseInt(requiredMatcher.group(1));
                int checkedMajor = Integer.parseInt(checkedMatcher.group(1));
                if (checkedMajor < requiredMajor) {
                    throw new IllegalStateException("GraalVM version " + requiredMajor + " is required but " + checkedMajor + " has been detected, please upgrade.");
                } else {
                    if (requiredMatcher.group(2) != null) {
                        int requiredMinor = Integer.parseInt(requiredMatcher.group(2));
                        int checkedMinor = Integer.parseInt(checkedMatcher.group(2));
                        if (checkedMinor < requiredMinor) {
                            throw new IllegalStateException("GraalVM version " + requiredMajor + "." + requiredMinor + " is required but " + checkedMajor + "." + checkedMinor + " has been detected, please upgrade.");
                        }

                        if (requiredMatcher.group(3) != null) {
                            int requiredPatch = Integer.parseInt(requiredMatcher.group(3));
                            int checkedPatch = Integer.parseInt(checkedMatcher.group(3));
                            if (checkedPatch < requiredPatch) {
                                throw new IllegalStateException("GraalVM version " + requiredMajor + "." + requiredMinor + "." + requiredPatch + " is required but " + checkedMajor + "." + checkedMinor + "." + checkedPatch + " has been detected, please upgrade.");
                            }
                        }
                    }

                }
            }
        }
    }
}

javac NativeImageUtils.java 编译得到NativeImageUtils.class

替换本地maven下载的utils-0.9.19.jar的NativeImageUtils.class

mvn_repo\org\graalvm\buildtools\utils\0.9.19\utils-0.9.19.jar\org\graalvm\buildtools\utils\NativeImageUtils.class

生成的exe文件如下

拷贝springboot-native-demo.exe和application.properties,并修改application.properties配置文件server.port=12345到部署目录D:\\deploy部署目录

双击exe启动项目

发现0.098秒启动,对比普通的java启动时间1.397秒

同时对比普通jar程序的部署(需要安装jdk,执行java命令或脚本等)

Graalvm Native-image真香😄

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值