【问题标题】: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 postvideo

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

使用它的最有用的链接是 start.spring.io,它现在提供 Spring Native 支持和 reference documentationgetting 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 seconds0.08 secondsexample 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-nativethis 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 docsthis 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 answerthis blog post。请参阅full compile process in action on TravisCI also

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        目前可以使用QuarkusMicronautVert.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
          相关资源
          最近更新 更多