在“ Spring框架开发人员 ”课程开始之前,准备了本文的翻译。
您好春天恋人!欢迎使用Spring Tips的下一个版本。今天,我们将讨论最近实现的在GraalVM中编译Spring Boot应用程序的支持。我们已经在另一个关于Spring Fu的Spring Tips版本中讨论了GraalVM和本机应用程序。让我们回顾一下GraalVM是什么。GraalVM替代了OpenJDK中的标准C1编译器。您可以与GraalVM贡献者和Twitter工程师Chris Thalinge 一起在Bootiful Podcast播客中阅读有关使用GraalVM的更多信息。在某些情况下,GraalVM可以让您更快地运行常规Spring应用程序,至少由于这个原因,它值得关注。但是我们不会谈论它。我们将研究其他GraalVM组件:本机图像生成器和SubstrateVM。 SubstrateVM允许您为Java应用程序创建本机可执行文件。顺便说一下,关于GraalVM的这种用途和其他用途,有来自Oracle Labs的Oleg Shelaev的播客。本机映像构建器是对折衷的测试。如果向GraalVM提供有关运行时应用程序行为的足够信息(动态链接库,反射,代理等),它将能够将Java应用程序转换为静态链接二进制文件,例如C或Golang中的应用程序。老实说,这个过程可能会很痛苦。但是,如果您这样做,则可以生成非常快的本机代码。结果,该应用程序将占用更少的RAM,并在不到一秒钟的时间内运行。不到一秒钟。很诱人,不是吗?当然!但是,应记住,必须考虑一些要点。生成的GraalVM二进制文件不是Java应用程序。它们甚至无法在常规JVM上运行。 GraalVM由Oracle Labs开发,Java和GraalVM团队之间存在某种互动,但是我不会将其称为Java。生成的二进制文件不会跨平台。该应用程序不使用JVM。它在另一个称为SubstrateVM的运行时中运行。因此,这里有很多折衷,但是我认为GraalVM具有巨大的潜力,尤其是对于规模和效率至关重要的基于云的应用程序。开始吧。安装GraalVM。您可以在此处下载它,或使用SDKManager安装。为了安装Java发行版,我喜欢使用SDKManager。GraalVM落后于Java的最新版本,并且当前支持Java 8和11。对Java 14或15或更高版本(阅读本文时将使用该版本)的支持已丢失。要为Java 8安装GraalVM,请运行:sdk install java 20.0.0.r8-grl
我建议使用Java 8而不是Java 11,因为Java 11中有些模糊的错误尚未弄清。之后,您需要安装本机映像构建器组件。运行:gu install native-image
gu
-这是GraalVM的实用程序。最后,检查什么JAVA_HOME
指向GraalVM。在我的机器(带有SDKMAN的Macintosh)上,我的JAVA_HOME
看起来像这样:export JAVA_HOME=$HOME/.sdkman/candidates/java/current/
现在您已经完成了所有设置,让我们看一下我们的应用程序。转到Spring Initializr并使用Lombok,R2DBC,PostgreSQL和Reactive Web生成一个新项目。您看到了类似的代码一百万次,所以我不会反汇编它,而只需在此处给出即可。package com.example.reactive;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.stream.Stream;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Log4j2
@SpringBootApplication(proxyBeanMethods = false)
public class ReactiveApplication {
@Bean
RouterFunction<ServerResponse> routes(ReservationRepository rr) {
return route()
.GET("/reservations", r -> ok().body(rr.findAll(), Reservation.class))
.build();
}
@Bean
ApplicationRunner runner(DatabaseClient databaseClient, ReservationRepository reservationRepository) {
return args -> {
Flux<Reservation> names = Flux
.just("Andy", "Sebastien")
.map(name -> new Reservation(null, name))
.flatMap(reservationRepository::save);
databaseClient
.execute("create table reservation ( id serial primary key, name varchar(255) not null )")
.fetch()
.rowsUpdated()
.thenMany(names)
.thenMany(reservationRepository.findAll())
.subscribe(log::info);
};
}
public static void main(String[] args) {
SpringApplication.run(ReactiveApplication.class, args);
}
}
interface ReservationRepository extends ReactiveCrudRepository<Reservation, Integer> {
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Reservation {
@Id
private Integer id;
private String name;
}
您可以在此处查看完整的代码。该应用程序的唯一功能是,我们使用Spring Boot属性proxyBeanMethods
来确保该应用程序将不使用cglib和JDK以外的其他代理。GraalVM不支持非JDK代理。尽管即使使用JDK代理,您也必须对其进行修改,以便GraalVM了解它们。Spring Framework 5.2新增的此属性部分旨在支持GraalVM。因此,让我们继续前进。前面我提到过,我们必须告诉GraalVM运行时可能在我们的应用程序中的某些要点,以及在执行本机代码时可能不理解的要点。这些是诸如反射,代理等之类的东西。有几种方法可以做到这一点。您可以手动描述配置并将其包括在装配中。 GraalVM将自动将其拾取。另一种方法是使用Java代理运行程序,该代理监视应用程序的运行情况,并在应用程序完成后将所有内容写入配置文件,然后将其传输到GraalVM编译器。您也可以使用GraalVM功能。(注意翻译:“功能” -术语GraalVM表示插件为本地编译,生成类文件中的本机二进制)。 GraalVM功能类似于Java代理。它可以进行某种分析,并将信息传递给GraalVM编译器。 Feature知道并了解Spring应用程序的工作方式。她知道何时四季豆是代理。她知道如何在运行时动态创建类。她知道Spring的工作原理,并且知道GraalVM的要求,至少在大多数时候(毕竟,这是一个早期版本!),您还需要配置构建。这是我的pom.xml
。<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.M4</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>reactive</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<start-class>
com.example.reactive.ReactiveApplication
</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-graal-native</artifactId>
<version>0.6.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<finalName>
${project.artifactId}
</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<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>
<profiles>
<profile>
<id>graal</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>20.0.0</version>
<configuration>
<buildArgs>
-Dspring.graal.mode=initialization-only -Dspring.graal.dump-config=/tmp/computed-reflect-config.json -Dspring.graal.verbose=true -Dspring.graal.skip-logback=true --initialize-at-run-time=org.springframework.data.r2dbc.connectionfactory.ConnectionFactoryUtils --initialize-at-build-time=io.r2dbc.spi.IsolationLevel,io.r2dbc.spi --initialize-at-build-time=io.r2dbc.spi.ConstantPool,io.r2dbc.spi.Assert,io.r2dbc.spi.ValidationDepth --initialize-at-build-time=org.springframework.data.r2dbc.connectionfactory -H:+TraceClassInitialization --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime -H:+ReportExceptionStackTraces --no-server --initialize-at-build-time=org.reactivestreams.Publisher --initialize-at-build-time=com.example.reactive.ReservationRepository --initialize-at-run-time=io.netty.channel.unix.Socket --initialize-at-run-time=io.netty.channel.unix.IovArray --initialize-at-run-time=io.netty.channel.epoll.EpollEventLoop --initialize-at-run-time=io.netty.channel.unix.Errors
</buildArgs>
</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>
</project>
在这里,我们注意插件native-image-maven-plugin
。他通过命令行获取参数,这有助于他了解需要执行的操作。所有这些参数buildArgs
对于启动应用程序都是必需的。 (我必须对Spring GraalVM功能维护者Andy Clement表示感谢,感谢他们帮助我找出了所有这些选择!)<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-graal-native</artifactId>
<version>0.6.0.RELEASE</version>
</dependency>
我们希望GraalVM编译器具有尽可能多的方式来提供有关应用程序应如何工作的信息:java代理,GraalVM功能,命令行参数。所有这些共同为GraalVM提供了足够的信息,可以成功地将应用程序转换为静态编译的本机二进制文件。从长远来看,我们的目标是Spring项目。Spring GraalVM功能提供了支持它们所需的一切。现在我们已经完成了所有设置,让我们将一个应用程序放在一起:- 以常规方式编译Java应用程序
- 使用Java代理启动Java应用程序以收集信息。在此阶段,我们需要确保该应用程序正在运行。您需要仔细研究所有可能的用例。顺便说一句,这是使用CI和测试的一个很好的例子!每个人都在不断谈论测试应用程序和提高性能。现在,借助GraalVM,您可以同时做到!
- 然后,使用活动的graal概要文件重新构建应用程序,以使用首次启动时收集的信息将其编译为本地应用程序。
mvn -DskipTests=true clean package
export MI=src/main/resources/META-INF
mkdir -p $MI
java -agentlib:native-image-agent=config-output-dir=${MI}/native-image -jar target/reactive.jar
## it's at this point that you need to exercise the application: http://localhost:8080/reservations
## then hit CTRL + C to stop the running application.
tree $MI
mvn -Pgraal clean package
如果一切顺利,那么在目录中target
您将看到一个已编译的应用程序。运行。./target/com.example.reactive.reactiveapplication
从类似于此的输出可以看出,该应用程序已启动。2020-04-15 23:25:08.826 INFO 7692 --- [ main] c.example.reactive.ReactiveApplication : Started ReactiveApplication in 0.099 seconds (JVM running for 0.103)
不错?GraalVM本机图像生成器非常适合与CloudFoundry或Kubernetes等云平台配对使用。您可以轻松地将应用程序组装到容器中,并以最少的资源在云中运行它。与往常一样,我们将很高兴收到您的来信。这项技术适合您吗?有什么问题吗 注释?Twitter(@springcentral)。
了解有关该课程的更多信息