4年前 (2021-06-03)  Serverless |   抢沙发  2513 
文章评分 0 次,平均分 0.0

本文基于上一篇:在Docker&Heroku上运行Spring Boot GraalVM本机映像

新的spring-graalvm-native原生版本0.7.1和graalvm 20.1.0都进行了大量的优化!本机映像命令的配置变得容易得多。那么让我们来看一下Spring Boot GraalVM本机映像编译的native-image-maven-plugin

新的Spring特性0.7.1版本&GraalVM 20.1.0

Spring的队伍真的很快!几天前,他们发布了spring-graalvm-native原生项目的新版本0.7.1,它再次优化了我们将springboot应用程序编译成graalvm原生映像的方式。如果你想知道更多关于如何使用它的知识,请查看本博客系列的第一篇文章

随着版本0.7.0的发布,Spring特性项目从Spring-graal-native重命名为Spring-graalvm-native!因此,在访问项目、文档或从Spring里程碑存储库下载最新的Maven依赖项时,不要感到困惑。

Spring实验项目spring-graalvm-native的最新版本现在基于springboot2.3.0.release和graalvm20.1.0。它改进了对Kotlin、Spring数据MongoDB和日志的支持。此外,它还附带了专用的功能性Spring应用程序支持,并进一步减少了内存占用。另外,GraalVM团队发布了新的GraalVM版本20.1.0,并进行了大量的改进——还包括Spring。

这个博客系列的示例项目的pom.xml已经更新:https://github.com/jonashackt/spring-boot-graalvm/blob/master/pom.xml。要使用新版本,只需更新Maven依赖项(不要忘了还准备好Spring Milestone repositories):https://repo.spring.io/milestone/org/springframework/experimental/spring-graalvm-native/

<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>

由于我们现在也可以利用Docker进行Spring Boot本机映像编译,因此示例项目的Dockerfile现在也使用最新的GraalVM版本:

FROM oracle/graalvm-ce:20.1.0-java11

从编译脚本到本机映像maven插件

spring-graalvm-native本机项目的新版本还附带了一些更微妙的变化,使得将springboot应用程序编译成graalvm本机映像变得更加容易。其中一个更改是关于本机映像命令所需的配置选项。许多参数现在只是默认启用。所以我们不需要再明确地定义它们了。尤其是--no-server--no-fallback,在使用新版本时可以省略。示例springwebflux应用程序的最后一个本机映像命令如下所示(有关更多详细信息,请参阅示例项目的compile.sh):

GRAALVM_VERSION=`native-image --version`
echo "[-->] Compiling Spring Boot App '$ARTIFACT' with $GRAALVM_VERSION"
time native-image \
  -J-Xmx4G \
  -H:+TraceClassInitialization \
  -H:Name=$ARTIFACT \
  -H:+ReportExceptionStackTraces \
  -Dspring.graal.remove-unused-autoconfig=true \
  -Dspring.graal.remove-yaml-support=true \
  -cp $CP $MAINCLASS;

但是有了一个更简单的native-image命令,现在是查看native-image-maven-plugin的好时机。

不要混淆org.graalvm.nativeimage.native-image-maven-plugin的包名!这个插件还有一个更老的版本,叫做com.oracle.substratevm.native-image-maven-plugin,不再维护了。

使用本机映像maven插件将主要取代第一篇文章中描述的步骤6、7和8,准备SpringBoot成为一个通用的本机映像友好型。但如果出了什么问题,知道幕后发生了什么仍然是件好事。我认为这也是Spring团队为每个示例项目准备compile.sh脚本的原因。

使用本机映像maven插件

为了使用这个插件,我们用一个名为native的Maven概要文件扩展pom.xml,如下所示:

<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>-J-Xmx4G -H:+TraceClassInitialization -H:+ReportExceptionStackTraces
                            -Dspring.graal.remove-unused-autoconfig=true -Dspring.graal.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>

buildArgs标记在这里非常重要!我们需要为Spring启动应用程序配置成功运行native-image命令所需的一切,就像compile.sh中已经使用的那样。同样,spring-boot-maven-plugin也需要在maven本机概要文件中,因为本机映像maven插件需要它才能正常工作。

我们可以省略-cp $CP $MAINCLASS参数,因为在使用Maven时已经提供了它。添加${project.artifactId}也是一个好主意,以便使用我们的artifactId作为结果可执行文件的名称。否则,我们将得到一个完全限定的类名,如io.jonashackt.springbootgraal.springboothelloapplication

正如compile.sh脚本中已经使用的那样,我们还需要准备好start class属性:

<properties>
    <start-class>io.jonashackt.springbootgraal.SpringBootHelloApplication</start-class>
...
</properties>

这可能就是我们需要做的一切。但是等等!我遇到了这个错误…

防止“找不到默认构造函数无法实例化java.lang.NoSuchMethodException”错误

使用带有mvn-Pnative clean包的新概要文件运行Maven构建成功地编译了我的Spring Boot应用程序。但当我试着运行它时,应用程序没有正常启动,出现了以下错误:

./target/spring-boot-graal
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::
 
Jun 05, 2020 10:46:27 AM org.springframework.boot.StartupInfoLogger logStarting
INFO: Starting application on PikeBook.fritz.box with PID 33047 (started by jonashecht in /Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target)
Jun 05, 2020 10:46:27 AM org.springframework.boot.SpringApplication logStartupProfileInfo
INFO: No active profile set, falling back to default profiles: default
Jun 05, 2020 10:46:27 AM org.springframework.context.support.AbstractApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springBootHelloApplication': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.jonashackt.springbootgraal.SpringBootHelloApplication]: No default constructor found; nested exception is java.lang.NoSuchMethodException: io.jonashackt.springbootgraal.SpringBootHelloApplication.<init>()
Jun 05, 2020 10:46:27 AM org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener logMessage
INFO:
 
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
Jun 05, 2020 10:46:27 AM org.springframework.boot.SpringApplication reportFailure
SEVERE: Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springBootHelloApplication': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.jonashackt.springbootgraal.SpringBootHelloApplication]: No default constructor found; nested exception is java.lang.NoSuchMethodException: io.jonashackt.springbootgraal.SpringBootHelloApplication.<init>()
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1320)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1214)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:62)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
	at io.jonashackt.springbootgraal.SpringBootHelloApplication.main(SpringBootHelloApplication.java:10)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.jonashackt.springbootgraal.SpringBootHelloApplication]: No default constructor found; nested exception is java.lang.NoSuchMethodException: io.jonashackt.springbootgraal.SpringBootHelloApplication.<init>()
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1312)
	... 18 more
Caused by: java.lang.NoSuchMethodException: io.jonashackt.springbootgraal.SpringBootHelloApplication.<init>()
	at java.lang.Class.getConstructor0(DynamicHub.java:3349)
	at java.lang.Class.getDeclaredConstructor(DynamicHub.java:2553)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
	... 19 more

我很难搞清楚这个!尤其是因为compile.sh的工作方式与本机映像maven插件完全没有区别。参数相同!但最后我发现了一个不同点——这都是关于Spring特性计算的spring.components!

运行compile.sh脚本时,Spring特性会动态计算一个spring.components文件,其中包含示例项目的3个类,这些类用一个典型的Spring@Component注释:

$ ./compile.sh
...
Excluding 104 auto-configurations from spring.factories file
Found no META-INF/spring.components -> synthesizing one...
Computed spring.components is
vvv
io.jonashackt.springbootgraal.HelloRouter=org.springframework.stereotype.Component
io.jonashackt.springbootgraal.HelloHandler=org.springframework.stereotype.Component
io.jonashackt.springbootgraal.SpringBootHelloApplication=org.springframework.stereotype.Component
^^^
Registered 3 entries
Configuring initialization time for specific types and packages:
#69 buildtime-init-classes   #21 buildtime-init-packages   #28 runtime-init-classes    #0 runtime-init-packages

使用native-image-maven-plugin,编译过程没有成功计算spring.components文件,因此无法识别三个带注释的类:

$ mvn -Pnative clean package
...
Excluding 104 auto-configurations from spring.factories file
Found no META-INF/spring.components -> synthesizing one...
Computed spring.components is
vvv
^^^
Registered 0 entries
Configuring initialization time for specific types and packages:
#69 buildtime-init-classes   #21 buildtime-init-packages   #28 runtime-init-classes    #0 runtime-init-packages

spring-context-indexer来拯救!

但是为什么我们需要在spring.components文件中包含所有这些类呢?这是因为我们从运行在SubstrateVM上的Spring Boot应用程序编译GraalVM本机映像,SubstrateVM的特性集非常小。使用本机映像不支持在运行时使用动态组件扫描!

这个问题的解决方案是在构建时进行组件扫描!一个已经做了相当长一段时间的实用程序是spring-context-indexer。使用native-image-maven-plugin,我们必须在pom.xml中显式包含spring-context-indexer依赖项:

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
    </dependency>

现在运行Maven构建,创建了包含我们需要的3个类的文件target/classes/META_NF/spring.components

io.jonashackt.springbootgraal.HelloHandler=org.springframework.stereotype.Component
io.jonashackt.springbootgraal.HelloRouter=org.springframework.stereotype.Component
io.jonashackt.springbootgraal.SpringBootHelloApplication=org.springframework.stereotype.Component

最后,我们的Maven构建工作如预期,并执行本地镜像编译像一个魅力!只需使用以下命令运行构建:

$ mvn -Pnative clean package

有关使用Maven编译Spring Boot GraalVM本机映像的完整示例,请查看这个TravisCI构建:https://travis-ci.com/github/jonashackt/spring-boot-graalvm/jobs/347877683

与Docker一起使用本机映像maven插件

正如我们在上一篇文章中了解到的,使用Docker和Heroku运行Spring Boot GraalVM本机映像,使用Docker编译Spring Boot本机映像是一个很好的组合。如果您遵循了本文中的所有步骤,并使用本机概要文件扩展pom.xml,那么将native-image-maven-plugin与Docker一起使用应该很容易。让我们看看Dockerfile:

# Simple Dockerfile adding Maven and GraalVM Native Image compiler to the standard
# https://hub.docker.com/r/oracle/graalvm-ce image
FROM oracle/graalvm-ce:20.1.0-java11
 
ADD . /build
WORKDIR /build
 
# For SDKMAN to work we need unzip & zip
RUN yum install -y unzip zip
 
RUN \
    # Install SDKMAN
    curl -s "https://get.sdkman.io" | bash; \
    source "$HOME/.sdkman/bin/sdkman-init.sh"; \
    sdk install maven; \
    # Install GraalVM Native Image
    gu install native-image;
 
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" && mvn --version
 
RUN native-image --version
 
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" && mvn -Pnative clean package
 
 
# We use a Docker multi-stage build here in order to only take the compiled native Spring Boot App from the first build container
FROM oraclelinux:7-slim
 
MAINTAINER Jonas Hecht
 
# Add Spring Boot Native app spring-boot-graal to Container
COPY --from=0 "/build/target/spring-boot-graal" spring-boot-graal
 
# Fire up our Spring Boot Native app by default
CMD [ "sh", "-c", "./spring-boot-graal -Dserver.port=$PORT" ]

在这里我们不需要做太多的更改——我们只需要使用Maven命令mvn -Pnative clean package,而不是compile.sh。此外,GraalVM基本映像也更新为oracle/GraalVM-ce:20.1.0-java11。如果您关注这个博客系列的文章,那么您还需要更改从这个多阶段Docker构建中的第一个构建容器复制本机映像的位置。由于我们使用的是Maven插件,因此生成的spring-boot-graal只驻留在/build/target/中。

使用maven插件简化Spring Boot GraalVM原生镜像构建

使用Docker build运行--tag=spring-boot-graal,然后在容器内通过以下方式启动本机编译的spring boot应用程序:

docker run -p 8080:8080 spring-boot-graal

使用本机映像maven插件编译我们的Spring Boot GraalVM本机映像很有趣!

尝试使用像Spring Boot GraalVM本机映像支持这样目前正在进行大量开发的技术有时会遇到挑战。在这里使用bash脚本来更深刻地理解幕后发生的事情绝对是有意义的。尤其是如果我们需要为编译创建一个正常工作的本机映像命令!

但正如前面所说的,Spring团队确实做得很好,而且Spring实验项目springgraalvm native的每一个版本所要求的配置都变得越来越简单。到了更稳定的版本,开始使用native-image-maven-plugin肯定是个好主意,我们已经习惯了,同时使用其他基于GraalVM的框架,比如Quarkus.io。正如我以前的同事Benedikt Ritter正确地说的,我们应该使用比bash脚本更现代的方式来构建我们今天的应用程序。

原文地址:https://blog.codecentric.de/en/2020/06/spring-boot-graalvm-native-image-maven-plugin/

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/1979.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册