Uma tradução do artigo foi preparada antes do início do curso "Developer on the Spring Framework" .
Olá amantes da primavera! Bem-vindo ao próximo lançamento do Spring Tips. Hoje falaremos sobre o suporte implementado recentemente para compilar aplicativos Spring Boot no GraalVM. Já falamos sobre o GraalVM e aplicativos nativos em outro lançamento do Spring Tips sobre o tópico do Spring Fu.Vamos lembrar o que é GraalVM. GraalVM é um substituto para o compilador C1 padrão no OpenJDK. Você pode ler mais sobre o uso do GraalVM no meu podcast Bootiful Podcast com Chris Thalinge, colaborador do GraalVM e engenheiro do Twitter. Sob certas condições, o GraalVM permite que você execute aplicativos regulares do Spring mais rapidamente e, pelo menos por esse motivo, merece atenção.Mas não vamos falar sobre isso. Veremos outros componentes do GraalVM: construtor de imagens nativo e SubstrateVM. O SubstrateVM permite criar executáveis nativos para o seu aplicativo Java. A propósito, sobre esse e outros usos do GraalVM, houve um podcast com Oleg Shelaev, do Oracle Labs. O construtor de imagens nativas é um teste de comprometimento. Se você fornecer ao GraalVM informações suficientes sobre o comportamento do seu aplicativo em tempo de execução (bibliotecas vinculadas dinamicamente, reflexão, proxies etc.), ele poderá transformar seu aplicativo Java em um binário vinculado estaticamente, como um aplicativo em C ou Golang. Honestamente, esse processo pode ser bastante doloroso. Mas se você fizer isso, poderá gerar código nativo que será incrivelmente rápido. Como resultado, o aplicativo ocupará muito menos RAM e será executado em menos de um segundo. Menos de um segundo. Bastante tentador, não é? Claro!No entanto, deve-se lembrar que alguns pontos devem ser levados em consideração. Os binários GraalVM resultantes não são aplicativos Java. Eles nem são executados em uma JVM regular. O GraalVM está sendo desenvolvido pelo Oracle Labs e existe algum tipo de interação entre as equipes Java e GraalVM, mas eu não chamaria isso de Java. O binário resultante não será multiplataforma. O aplicativo não usa a JVM. Ele é executado em outro tempo de execução chamado SubstrateVM.Portanto, existem muitas vantagens e desvantagens aqui, mas, no entanto, acho que o GraalVM tem um grande potencial, especialmente para aplicativos baseados em nuvem, onde a escalabilidade e a eficiência são fundamentais.Vamos começar. Instale o GraalVM. Você pode baixá-lo aqui ou instalar usando o SDKManager. Para instalar distribuições Java, eu gosto de usar o SDKManager. O GraalVM está um pouco atrás das versões mais recentes do Java e atualmente suporta Java 8 e 11. O suporte para Java 14 ou 15 ou posterior (que versão estará disponível quando você ler isso) está ausente.Para instalar o GraalVM for Java 8, execute:sdk install java 20.0.0.r8-grl
Eu recomendo usar o Java 8 em vez do Java 11, pois existem alguns erros obscuros no Java 11 que eu ainda não descobri.Depois disso, você precisa instalar o componente nativo do construtor de imagens. Execute:gu install native-image
gu
- este é um utilitário do GraalVM.Por fim, verifique o que JAVA_HOME
aponta para o GraalVM. Na minha máquina (Macintosh com SDKMAN), a minha JAVA_HOME
fica assim:export JAVA_HOME=$HOME/.sdkman/candidates/java/current/
Agora que você já configurou tudo, vamos dar uma olhada em nosso aplicativo. Vá para Spring Initializr e gere um novo projeto usando Lombok, R2DBC, PostgreSQL e Reactive Web.Você viu um código semelhante um milhão de vezes, então não vou desmontá-lo, mas apenas fornecê-lo aqui.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;
}
Você pode ver o código completo aqui .O único recurso desse aplicativo é que usamos o atributo Spring Boot proxyBeanMethods
para garantir que o aplicativo não use cglib e outros proxies que não sejam o JDK. O GraalVM não suporta proxies não-JDK. Embora mesmo com o proxy JDK, você precise mexer nele para que o GraalVM aprenda sobre eles. Esse atributo, novo no Spring Framework 5.2, destina-se em parte ao suporte ao GraalVM.Então, vamos seguir em frente. Mencionei anteriormente que precisamos informar o GraalVM sobre alguns pontos que podem estar em nosso aplicativo em tempo de execução e que ele pode não entender ao executar código nativo. São coisas como reflexão, proxies, etc. Existem várias maneiras de fazer isso. Você pode descrever a configuração manualmente e incluí-la na montagem. O GraalVM irá buscá-lo automaticamente. Outra maneira é executar o programa com um agente Java que monitora o que o aplicativo está fazendo e, depois que o aplicativo termina, grava tudo nos arquivos de configuração, que podem ser transferidos para o compilador GraalVM.Você também pode usar o recurso GraalVM. (Nota Tradutor: “feature” - o termo GraalVM indica um plug-in para compilação nativa, criando um binário nativo a partir de um arquivo de classe ) . O recurso GraalVM é semelhante a um agente Java. Pode fazer algum tipo de análise e passar informações para o compilador GraalVM. O recurso conhece e entende como o aplicativo Spring funciona. Ela sabe quando os beans Spring são proxies. Ela sabe como criar classes dinamicamente em tempo de execução. Ela sabe como o Spring funciona, e sabe o que o GraalVM deseja, pelo menos na maioria das vezes (afinal, esse é um release antecipado!)Você também precisa configurar a compilação. Aqui está o meupom.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>
Aqui prestamos atenção ao plugin native-image-maven-plugin
. Ele leva os parâmetros pela linha de comando, o que o ajuda a entender o que precisa ser feito. Todos esses parâmetros são buildArgs
necessários para o aplicativo iniciar. (Tenho que expressar minha profunda gratidão a Andy Clement, o mantenedor do Spring GraalVM Feature, por me ajudar a descobrir todas essas opções!)<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-graal-native</artifactId>
<version>0.6.0.RELEASE</version>
</dependency>
Queremos que o compilador GraalVM tenha o maior número de maneiras possível para fornecer informações sobre como o aplicativo deve funcionar: agente java, recurso GraalVM, parâmetros da linha de comando. Tudo isso fornece ao GraalVM informações suficientes para transformar com êxito o aplicativo em um binário nativo compilado estaticamente. A longo prazo, nosso objetivo são os projetos da primavera. E o recurso Spring GraalVM fornece tudo o que você precisa para suportá-los.Agora que já temos tudo configurado, vamos montar um aplicativo:- Compile o aplicativo Java da maneira usual
- Inicie um aplicativo Java com um agente Java para coletar informações. Nesta fase, precisamos garantir que o aplicativo esteja funcionando. Você precisa passar por todos os casos de uso possíveis. A propósito, este é um caso muito bom para usar CI e testes! Todo mundo está constantemente falando sobre testar o aplicativo e melhorar o desempenho. Agora, com o GraalVM, você pode fazer as duas coisas!
- Em seguida, recrie o aplicativo, desta vez com o perfil graal ativo, para compilá-lo no aplicativo nativo, usando as informações coletadas durante o primeiro lançamento.
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
Se tudo ocorreu sem erros, no diretório target
você verá um aplicativo compilado. Executá-lo../target/com.example.reactive.reactiveapplication
O aplicativo é iniciado, como pode ser visto em uma saída semelhante a esta.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)
Não é ruim? O construtor de imagens nativas do GraalVM é ótimo para emparelhado com uma plataforma em nuvem como CloudFoundry ou Kubernetes. Você pode montar o aplicativo facilmente em um contêiner e executá-lo na nuvem com o mínimo de recursos.Como sempre, teremos o maior prazer em ouvir de você. Esta tecnologia é adequada para você? Questões? Comentários? Twitter (@springcentral) .
Saiba mais sobre o curso