正如Oleg Šelajev 所述,使用GraalVM Native Image(这是GraalVM 的子项目)对Spring Boot 应用程序进行本地编译是可能的,但目前有限制,is planned to be released with the Spring Framework’s 5.3 release in autumn 2020。使用 Native Image,您可以在内存占用和启动时间减少方面获得与使用 Quarkus.io、Micronaut 等类似的优势。我能够将内存占用从 500MB 减少到 30MB,启动时间从1.5 seconds 到 0.08 seconds 在 example project implementing a Reactive Spring Boot Web app 中。
简而言之,如果您想在生产中使用该功能,您必须等待 2020 年末的最终 Spring 5.3 版本以及基于它的 Spring Boot 版本。如果您想开始试验性地使用该功能,您可以立即开始。
====== 更新为spring-graalvm-native 0.7.0 release at June 10, 2020。 =======
这是我最近写的spring-projects-experimental project spring-graalvm-native 和this blog post 的基本步骤(2020 年 6 月)、derived from the latest docs(步骤 7 和 8 可以通过 compile.sh bash 来实现脚本或在native-image-maven-plugin 的帮助下 - 两种替代方法都在下面解释):
- 编写您的 Spring Boot 应用程序(例如从 https://start.spring.io 重新开始或使用现有应用程序)
- 在 pom.xml 中升级到最新版本的 Spring Boot 2.3.x(不要从 2.2 或更低版本开始!)
- 在 pom.xml 中设置 start-class 元素
native-image 命令稍后需要@SpringBootApplication 注释类的完全限定类名。在 pom.xml 的属性中定义它,如下所示:
<properties>
...
<start-class>io.jonashackt.springbootgraal.SpringBootHelloApplication</start-class>
</properties>
- 禁用 GCLIB 代理
由于 GraalVM 不支持 GCLIB 代理,因此 Spring Boot 需要 use JDK proxies instead。因此使用 @SpringBootApplication 类的 proxyBeanMethods = false 属性:
@SpringBootApplication(proxyBeanMethods = false)
public class SpringBootHelloApplication {
...
}
- 安装 GraalVM 20.1.0 和 GraalVM Native Image 命令
最简单的方法是使用 SDKMAN:
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 20.1.0.r11-grl
gu install native-image
输入java -version(应该列出GraalVM)和native-image --version,检查两者是否正常工作。见this blog post for more details。
- 将 Annotation 类路径扫描从运行时重新定位到构建时 & 6. 检测自动配置
这两个步骤都由 Spring Graal @AutomaticFeature 为您完成,稍后使用 native-image 命令。作为@AutomaticFeature was already released on Spring Milestones repository,我们可以简单地向我们的pom.xml 添加一个依赖项(不要忘记现在还添加Spring Milestones 存储库,因为它现在不通过Maven Central 提供):
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-graalvm-native</artifactId>
<version>0.7.1</version>
</dependency>
...
<dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
- 准备执行本机映像命令
本质上,我们需要为native-image命令准备配置变量,然后构建应用程序,扩展Spring Boot fat JAR并配置类路径。我创建了一个compile.sh,它使用 bash 执行必要的步骤:
#!/usr/bin/env bash
echo "[-->] Detect artifactId from pom.xml"
ARTIFACT=$(mvn -q \
-Dexec.executable=echo \
-Dexec.args='${project.artifactId}' \
--non-recursive \
exec:exec);
echo "artifactId is '$ARTIFACT'"
echo "[-->] Detect artifact version from pom.xml"
VERSION=$(mvn -q \
-Dexec.executable=echo \
-Dexec.args='${project.version}' \
--non-recursive \
exec:exec);
echo "artifact version is '$VERSION'"
echo "[-->] Detect Spring Boot Main class ('start-class') from pom.xml"
MAINCLASS=$(mvn -q \
-Dexec.executable=echo \
-Dexec.args='${start-class}' \
--non-recursive \
exec:exec);
echo "Spring Boot Main class ('start-class') is '$MAINCLASS'"
echo "[-->] Cleaning target directory & creating new one"
rm -rf target
mkdir -p target/native-image
echo "[-->] Build Spring Boot App with mvn package"
mvn -DskipTests package
echo "[-->] Expanding the Spring Boot fat jar"
JAR="$ARTIFACT-$VERSION.jar"
cd target/native-image
jar -xvf ../$JAR >/dev/null 2>&1
cp -R META-INF BOOT-INF/classes
echo "[-->] Set the classpath to the contents of the fat jar & add the Spring Graal AutomaticFeature to the classpath"
LIBPATH=`find BOOT-INF/lib | tr '\n' ':'`
CP=BOOT-INF/classes:$LIBPATH
- 制作 native-image 命令并运行编译
现在我们已经准备好制作并最终运行native-image 命令。这是一个示例,它基于提到的example project implementing a Reactive Spring Boot Web app。这个现在很棘手,并且取决于您要编译为 GraalVM Native Image 的那种 Spring Boot 应用程序!因此最好的方法是从the example projects of the spring-graal-native project获得一些灵感:
GRAALVM_VERSION=`native-image --version`
echo "[-->] Compiling Spring Boot App '$ARTIFACT' with $GRAALVM_VERSION"
time native-image \
-H:+TraceClassInitialization \
-H:Name=$ARTIFACT \
-H:+ReportExceptionStackTraces \
-Dspring.native.remove-unused-autoconfig=true \
-Dspring.native.remove-yaml-support=true \
-cp $CP $MAINCLASS;
latest docs或this blog post中对每个参数也有全面的解释。
最后通过./compile.sh 执行bash 脚本并喝杯咖啡!这需要一些时间,具体取决于您的硬件!在我迟到的 MBP 2017 上,示例项目大约需要 3-4 分钟。如果一切顺利,您将在 /target/native-image/spring-boot-graal 中找到您本机编译的 Spring Boot 应用程序。只需运行它:
./target/native-image/spring-boot-graal
================================
7 和 8 的替代方案:native-image-maven-plugin
除了 bash 脚本(以及描述的步骤 7 和 8)之外,还有 native-image-maven-plugin。但请仅在您确定如何配置native-image 命令的情况下使用它——因为它现在的执行非常麻烦(我相信到 2020 年底会有很多改进)。如果你想使用插件,步骤代替7和8如下:
- 添加 spring-context-indexer 依赖项
由于 @AutomaticFeature 在 native-image-maven-plugin 使用时不会自动探索所需的 Spring 组件(这是一个错误吗?),我们需要显式添加 spring-context-indexer 来完成这项工作:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
</dependency>
它会创建一个target/classes/META_INF/spring.components 文件,然后由本机映像编译过程拾取。
- 将 native-image-maven-plugin 添加为 Maven 构建配置文件
为了使native-image-maven-plugin 正常工作,最好为本地映像编译创建一个新的 Maven 配置文件(请参阅this pom.xml for a fully working example):
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>20.1.0</version>
<configuration>
<buildArgs>-H:+TraceClassInitialization -H:+ReportExceptionStackTraces -Dspring.native.remove-unused-autoconfig=true -Dspring.native.remove-yaml-support=true</buildArgs>
<imageName>${project.artifactId}</imageName>
</configuration>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
我们需要再次添加spring-boot-maven-plugin,因为它为原生图像插件准备了必要的配置。
关键部分是buildArgs 标签,它需要继承native-image 命令的参数,如compile.sh 脚本中所示。与那个相比,我们可以省略 -cp $CP $MAINCLASS 参数,因为插件识别包含主类本身的类路径(如果设置了步骤 3 中的 start-class 标记,则仅后者)。使用<imageName>${project.artifactId}</imageName> 是一个好主意,以便使用我们的artifactId 作为生成的可执行映像名称。
现在只需通过以下方式执行 Maven 配置文件:
mvn -Pnative clean package
如果编译成功,启动你的原生 Spring Boot 应用:
./target/spring-boot-graal
================================
如果你想在 TravisCI 这样的 CI 服务器上运行原生镜像编译或使用 Docker 进行编译,我可以recomment this so answer 和this blog post。请参阅full compile process in action on TravisCI also。