【问题标题】:Compiling Spring Applications to GraalVM Native Images将 Spring 应用程序编译为 GraalVM 原生镜像
【发布时间】:2018-06-18 14:18:45
【问题描述】:

GraalVM 系统显然无法将 Spring 应用程序编译为原生镜像。

我们能否编译 Spring 应用程序的一个子集——比如说,作为一个单独的库——然后将其与使用通常的 javac 编译器编译的其余部分一起使用?

或者如果我们从应用程序中遗漏了一些 Spring 特性?

还有其他可能吗?

【问题讨论】:

  • 当你说 GraalVM 无​​法编译 Spring 应用程序时,我假设你的意思是使用 native-image?
  • 是的,我就是这个意思。谢谢。我现在已经更正了这个问题。
  • Java 微服务空间最近添加了一些内容,它们编译为本机代码,占用一小部分空间,相当于 Spring 应用程序会占用,而且启动和执行时间也很短。示例有:Micronaut、Helidon、Quarkus,...此外,Vert.X 可以编译为本机代码。
  • 如何将标题更新为 «Compiling Spring applications to GraalVM native images» 以消除标题的歧义,就像您在描述中所做的那样?
  • 感谢您的建议;标题已更改。

标签: spring graalvm


【解决方案1】:

Spring Native 测试版的发布可能提供了对这个问题的最新答案,我们(Spring 团队)刚刚发布了 very detailed blog post 和 video。

它允许您通过 Spring Boot mvn spring-boot:build-image 或 gradle bootBuildImage 命令使用 GraalVM 本机映像编译器将 Spring 应用程序编译为本机可执行文件,或者通过 native-image-maven-plugin 仅利用本地 native-image 安装。

使用它的最有用的链接是 start.spring.io,它现在提供 Spring Native 支持和 reference documentation 的 getting started 部分。

确保正确配置Spring AOT Maven and Gradle plugins,这是为您的 Spring 应用程序获得适当本机支持所必需的。

享受吧!

【讨论】:

    【解决方案2】:

    这个问题的开场白有点含糊,所以很难正确解决。

    GraalVM 绝对可以编译 Spring 应用程序。 GraalVM 分发与普通的 JDK 非常相似,它包括一个javac 实用程序,一个java 实用程序,可以添加到路径并正常使用。 您可以设置$JAVA_HOME 环境变量指向您解压GraalVM 发行版的目录,将$JAVA_HOME/bin 添加到路径中,并以您通常构建它们的方式构建Spring 应用程序,使用Maven 或Gradle,或任何其他构建工具.

    GraalVM 还可以运行 Spring 应用程序,由它自己和其他 JVM 编译。如果你好奇,这里是an example of a Spring application,它不仅在 GraalVM 上运行,而且还使用 R 来可视化数据图,使用 GraalVM 多语言功能。

    现在,我猜你的意思是 GraalVM 能够create executable native images of some Java programs。

    更新:2021 年 3 月 13 日

    Spring Native 项目处于测试阶段,您可以使用它。 Sébastien Deleuze 接受的答案是一个很好的调查起点:https://stackoverflow.com/a/66596191/1087978

    更新:2019 年 11 月 17 日

    一些 Spring 应用程序作为 GraalVM 原生镜像工作。 Pivotal 和 GraalVM 团队正在积极努力改善支持。这是来自 Devoxx Belgium 2019 的 Sébastien Deleuze 的一场关于 Spring 应用程序和 GraalVM 原生镜像状态的会议,他展示了一个小型 hello world Spring 应用程序作为原生镜像工作,以及使用 JPA 和内存数据库作为原生工作的 vanilla Spring Petclinic 演示图片:https://www.youtube.com/watch?v=3eoAxphAUIg

    您可以按照此处的说明:https://github.com/spring-projects-experimental/spring-graalvm-native 构建或调查示例。

    请注意,这个项目是实验性的,因为它在自述文件中也有说明。

    对本机映像的支持尚未优化,它会变得更好,目前如果我尝试此存储库中的 spring-petclinic-jpa 示例,它可以在我功能不强的 macbook 上在 200 毫秒左右开始:

    14:13:11.990 [main] INFO  o.s.s.petclinic.PetClinicApplication - 
                 Started PetClinicApplication in 0.171 seconds (JVM running for 0.173)
    

    上一次更新:2019 年 5 月 17 日

    这是 spring-framework 的 wiki page for GraalVM native image support。

    由 Andy Clement 创建的 spring-graalvm-native 实验项目展示了如何将 Spring Boot 应用程序作为 GraalVM 原生映像开箱即用地运行。它可以用作潜在的即将到来的官方支持的基础。

    总而言之,您可以尝试一下,但事情可能无法完全按预期进行。

    之前的答案如下:

    有一个 spring-fu 项目,一个基于功能配置的实验性 Kotlin 微框架,旨在测试未来 Spring Boot 版本的新想法,目前是 experimenting,能够被 GraalVM 编译为原生图像。

    同时,GraalVM 团队正在研究可以采取哪些措施来简化将 Spring 应用程序编译为原生映像并支持比目前更多的 Spring 应用程序。一些限制仍然存在,因此您将始终无法构建不能作为 GraalVM 原生映像运行的 Spring 应用程序,但也许您将能够构建也可以工作的 Spring 应用程序。
    这些变化的确切路线图目前尚不清楚。

    这里有一个SpringFramework issue tracker ticket,大家可以关注它来查看发展情况。

    【讨论】:

    • 是的——我的意思是“编译成原生图像”。
    • 而且我想我们总是可以使用 JNI 混合编译为本机代码和 JIT 编译代码。因此,理想的完整系统应该是这样一种系统,它可以在同一应用程序中以某种方式分离出两种类型的代码(可编译为原生代码与仅可编译为 JIT 编译),然后将可以编译的内容编译为原生代码,并且然后使用 JNI 自动透明地链接两者。
    • 是的,您可以将 Java 代码编译为原生库,例如,请参阅 10 件事帖子中的第 8 点:medium.com/graalvm/graalvm-ten-things-12d9111f307d。我不是 100% 确定这样做的动机,但这是可能的。
    • 立即浮现在脑海中的两个动机是:速度、在版权未有效执行的地区对商业发行软件的版权保护。
    • 将有助于列出本机映像(GraalVM 的 compile-to-native-code 实用程序)可以处理的 Spring 构造。
    【解决方案3】:

    正如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 的帮助下 - 两种替代方法都在下面解释):

    1. 编写您的 Spring Boot 应用程序(例如从 https://start.spring.io 重新开始或使用现有应用程序)
    2. 在 pom.xml 中升级到最新版本的 Spring Boot 2.3.x(不要从 2.2 或更低版本开始!)
    3. 在 pom.xml 中设置 start-class 元素

    native-image 命令稍后需要@SpringBootApplication 注释类的完全限定类名。在 pom.xml 的属性中定义它,如下所示:

    <properties>
            ...
            <start-class>io.jonashackt.springbootgraal.SpringBootHelloApplication</start-class>
    </properties>
    
    1. 禁用 GCLIB 代理

    由于 GraalVM 不支持 GCLIB 代理,因此 Spring Boot 需要 use JDK proxies instead。因此使用 @SpringBootApplication 类的 proxyBeanMethods = false 属性:

    @SpringBootApplication(proxyBeanMethods = false)
    public class SpringBootHelloApplication {
        ...
    }
    
    1. 安装 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。

    1. 将 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>
    
    1. 准备执行本机映像命令

    本质上,我们需要为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
    
    1. 制作 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如下:

    1. 添加 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 文件,然后由本机映像编译过程拾取。

    1. 将 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 标记,则仅后者)。使用&lt;imageName&gt;${project.artifactId}&lt;/imageName&gt; 是一个好主意,以便使用我们的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。

    【讨论】:

      【解决方案4】:

      截至 2020 年 5 月,Spring 已发布 Spring Graalvm Native。 Spring Graalvm Native

      【讨论】:

        【解决方案5】:

        目前可以使用Quarkus、Micronaut 和Vert.X 等替代方案,它们具有与 Spring 兼容的特性或可与 Spring 一起使用。它们都可以编译成小的原生代码二进制可执行文件,可以立即启动并且占用很小的内存。

        这些新框架通过在构建时解释注释和其他规范来避免 GraalVM 的 limitations。这样他们就避免了 GraalVM AOT 编译器 native-image 无法支持的 Java 运行时特性。

        【讨论】:

          猜你喜欢
          • 2020-09-09
          • 2021-05-10
          • 2020-12-04
          • 1970-01-01
          • 2020-10-25
          • 1970-01-01
          • 1970-01-01
          • 2021-09-09
          • 2020-09-18
          相关资源
          最近更新 更多