1. Visão geral
Neste codelab, vamos aprender sobre o projeto Spring Native, criar um app que o usa e implantá-lo no Google Cloud.
Vamos analisar os componentes, o histórico recente do projeto, alguns casos de uso e, é claro, as etapas necessárias para usá-lo nos seus projetos.
O projeto Spring Native está em fase experimental. Portanto, ele exige uma configuração específica para começar. No entanto, conforme anunciado na SpringOne 2021, o Spring Native será integrado ao Spring Framework 6.0 e ao Spring Boot 3.0 com suporte de primeira classe. Portanto, este é o momento perfeito para analisar o projeto alguns meses antes do lançamento.
Embora a compilação just-in-time tenha sido muito bem otimizada para processos de longa duração, há alguns casos de uso em que os aplicativos compilados antecipadamente têm um desempenho ainda melhor, o que vamos discutir durante o codelab.
Você vai aprender a
- Usar o Cloud Shell
- Ativar a API Cloud Run
- Criar e implantar um app nativo Spring
- Implantar um app no Cloud Run
O que é necessário
- Um projeto do Google Cloud Platform com uma conta de faturamento do GCP ativa
- A CLI gcloud instalada ou acesso ao Cloud Shell
- Habilidades básicas em Java e XML
- Conhecimento prático de comandos comuns do Linux
Pesquisa
Como você usará este tutorial?
Como você classificaria sua experiência com Java?
Como você classificaria sua experiência de uso dos serviços do Google Cloud?
2. Contexto
O projeto Spring Native usa várias tecnologias para oferecer desempenho de apps nativos aos desenvolvedores.
Para entender totalmente o Spring Native, é útil entender algumas dessas tecnologias de componentes, o que elas permitem e como funcionam juntas.
Compilação AOT
Quando os desenvolvedores executam o javac normalmente no tempo de compilação, nosso código-fonte .java é compilado em arquivos .class gravados em bytecode. Esse bytecode só pode ser entendido pela Java Virtual Machine. Portanto, a JVM precisa interpretar esse código em outras máquinas para que possamos executar nosso código.
Esse processo é o que nos dá a portabilidade de assinatura do Java, permitindo que "escrevamos uma vez e executemos em qualquer lugar", mas é caro quando comparado à execução de código nativo.
Felizmente, a maioria das implementações da JVM usa a compilação just-in-time para atenuar esse custo de interpretação. Isso é feito contando as invocações de uma função e, se ela for invocada com frequência suficiente para passar um limite ( 10.000 por padrão), ela será compilada para código nativo no tempo de execução para evitar uma interpretação mais cara.
A compilação antecipada adota a abordagem oposta, compilando todo o código acessível em um executável nativo no tempo de compilação. Isso troca a portabilidade pela eficiência de memória e outros ganhos de desempenho no tempo de execução.

Essa é uma troca e nem sempre vale a pena. No entanto, a compilação AOT pode brilhar em determinados casos de uso, como:
- Aplicativos de curta duração em que o tempo de inicialização é importante
- Ambientes com restrição de memória em que o JIT pode ser muito caro
Como um fato divertido, a compilação AOT foi introduzida como um recurso experimental no JDK 9, embora essa implementação fosse cara de manter e nunca tenha sido muito usada. Portanto, ela foi removida silenciosamente no Java 17 em favor dos desenvolvedores que usam o GraalVM.
GraalVM
O GraalVM é uma distribuição de JDK de código aberto altamente otimizada que oferece tempos de inicialização extremamente rápidos, compilação de imagens nativas AOT e recursos poliglota que permitem que os desenvolvedores misturem várias linguagens em um único aplicativo.
O GraalVM está em desenvolvimento ativo, ganhando novos recursos e melhorando os atuais o tempo todo. Por isso, recomendo que os desenvolvedores fiquem atentos.
Alguns marcos recentes são:
- Uma nova saída de build de imagem nativa fácil de usar ( 18/01/2021)
- Suporte ao Java 17 ( 18/01/2022)
- Ativação da compilação de várias camadas por padrão para melhorar os tempos de compilação poliglota ( 20/04/2021)
Spring Native
Em termos simples, o Spring Native permite o uso do compilador de imagens nativas do GraalVM para transformar aplicativos Spring em executáveis nativos.
Esse processo envolve a execução de uma análise estática do aplicativo no tempo de compilação para encontrar todos os métodos no aplicativo que podem ser acessados pelo ponto de entrada.
Isso essencialmente cria uma concepção de "mundo fechado" do aplicativo, em que todo o código é considerado conhecido no tempo de compilação e nenhum novo código pode ser carregado no tempo de execução.
É importante observar que a geração de imagens nativas é um processo com uso intensivo de memória que leva mais tempo do que a compilação de um aplicativo normal e impõe limitações a determinados aspectos do Java.
Em alguns casos, nenhuma mudança de código é necessária para que um aplicativo funcione com o Spring Native. No entanto, algumas situações exigem uma configuração nativa específica para funcionar corretamente. Nessas situações, o Spring Native geralmente fornece dicas nativas para simplificar esse processo.
3. Configuração/Pré-trabalho
Antes de começar a implementar o Spring Native, precisamos criar e implantar nosso app para estabelecer um valor de referência de desempenho que possa ser comparado à versão nativa mais tarde.
1. Como criar o projeto
Vamos começar a receber nosso app de start.spring.io:
curl https://start.spring.io/starter.zip -d dependencies=web \
-d javaVersion=11 \
-d bootVersion=2.6.4 -o io-native-starter.zip
Esse app inicial usa o Spring Boot 2.6.4, que é a versão mais recente com suporte do projeto spring-native no momento da redação.
Observe que, desde o lançamento do GraalVM 21.0.3, você também pode usar o Java 17 para esta amostra. Ainda vamos usar o Java 11 neste tutorial para minimizar a configuração envolvida.
Depois de ter o arquivo zip na linha de comando, podemos criar um subdiretório para nosso projeto e descompactar a pasta nele:
mkdir spring-native cd spring-native unzip ../io-native-starter.zip
2. Mudanças no código
Depois de abrir o projeto, vamos adicionar rapidamente um sinal de vida e mostrar o desempenho do Spring Native quando o executarmos.
Edite o DemoApplication.java para que ele corresponda a este:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.time.Instant;
@RestController
@SpringBootApplication
public class DemoApplication {
private static Instant startTime;
private static Instant readyTime;
public static void main(String[] args) {
startTime = Instant.now();
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/")
public String index() {
return "Time between start and ApplicationReadyEvent: "
+ Duration.between(startTime, readyTime).toMillis()
+ "ms";
}
@EventListener(ApplicationReadyEvent.class)
public void ready() {
readyTime = Instant.now();
}
}
Nesse momento, nosso app de linha de base está pronto. Portanto, crie uma imagem e execute-a localmente para ter uma ideia do tempo de inicialização antes de convertê-la em um app nativo.
Para criar nossa imagem:
mvn spring-boot:build-image
Você também pode usar docker images demo para ter uma ideia do tamanho da imagem de linha de base: 
Para executar nosso app:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
3. Implantar o app de linha de base
Agora que temos nosso app, vamos implantá-lo e anotar os tempos, que serão comparados aos tempos de inicialização do app nativo mais tarde.
Dependendo do tipo de aplicativo que você está criando, há várias maneiras de hospedar seu conteúdo.
No entanto, como nosso exemplo é um aplicativo da Web muito simples e direto, podemos manter as coisas simples e confiar no Cloud Run.
Se você estiver seguindo as instruções na sua própria máquina, instale e atualize a ferramenta da CLI gcloud.
Se você estiver no Cloud Shell, tudo será cuidado e você poderá simplesmente executar o seguinte no diretório de origem:
gcloud run deploy
4. Configuração do aplicativo
1. Como configurar nossos repositórios Maven
Como esse projeto ainda está na fase experimental, precisamos configurar nosso app para encontrar artefatos experimentais, que não estão disponíveis no repositório central do Maven.
Isso envolve adicionar os seguintes elementos ao nosso pom.xml, que você pode fazer no editor de sua preferência.
Adicione as seções repositories e pluginRepositories ao nosso pom:
<repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
2. Como adicionar nossas dependências
Em seguida, adicione a dependência spring-native, que é necessária para executar um aplicativo Spring como uma imagem nativa. Observação: essa etapa não é necessária se você estiver usando o Gradle
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
3. Como adicionar/ativar nossos plug-ins
Agora adicione o plug-in AOT para melhorar a compatibilidade e a pegada da imagem nativa ( saiba mais):
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.2</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
Agora vamos atualizar o spring-boot-maven-plugin para ativar o suporte a imagens nativas e usar o builder paketo para criar nossa imagem nativa:
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
</plugins>
Observe que a imagem do builder tiny é apenas uma das várias opções. É uma boa opção para nosso caso de uso porque tem poucas bibliotecas e utilitários extras, o que ajuda a minimizar nossa superfície de ataque.
Se, por exemplo, você estivesse criando um app que precisasse de acesso a algumas bibliotecas C comuns ou ainda não tivesse certeza dos requisitos do app, o builder completo seria mais adequado.
5. Criar e executar o app nativo
Depois que tudo estiver no lugar, poderemos criar nossa imagem e executar nosso app nativo compilado.
Antes de executar o build, considere algumas coisas:
- Isso vai levar mais tempo do que um build normal (alguns minutos)

- Esse processo de build pode usar muita memória (alguns gigabytes)

- Esse processo de build exige que o daemon do Docker seja acessível
- Embora neste exemplo estejamos passando pelo processo manualmente, também é possível configurar as fases de build para acionar automaticamente um perfil de build nativo.
Para criar nossa imagem:
mvn spring-boot:build-image
Depois de criado, estamos prontos para ver o app nativo em ação.
Para executar nosso app:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
Nesse momento, estamos em uma ótima posição para ver os dois lados da equação do app nativo.
Perdemos um pouco de tempo e uso extra da memória no tempo de compilação, mas, em troca, recebemos um aplicativo que pode ser iniciado muito mais rapidamente e consumir significativamente menos memória (dependendo da carga de trabalho).
Se executarmos docker images demo para comparar o tamanho da imagem nativa com a original, veremos uma redução drástica:

Também precisamos observar que, em casos de uso mais complexos, são necessárias outras modificações para informar ao compilador AOT o que o app fará no tempo de execução. Por esse motivo, algumas cargas de trabalho previsíveis (como jobs em lote) podem ser muito adequadas para isso, enquanto outras podem ser mais difíceis.
6. Como implantar nosso app nativo
Para implantar nosso app no Cloud Run, precisamos colocar nossa imagem nativa em um gerenciador de pacotes como o Artifact Registry.
1. Como preparar nosso repositório do Docker
Podemos iniciar esse processo criando um repositório:
gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"
Em seguida, precisamos verificar se estamos autenticados para enviar por push ao novo registro.
A CLI gcloud pode simplificar bastante esse processo:
gcloud auth configure-docker us-central1-docker.pkg.dev
2. Como enviar nossa imagem para o Artifact Registry
Em seguida, vamos marcar nossa imagem:
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
docker tag demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
E então podemos usar docker push para enviá-la ao Artifact Registry:
docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
3. Como implantar no Cloud Run
Agora estamos prontos para implantar a imagem que armazenamos no Artifact Registry no Cloud Run:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
Como criamos e implantamos nosso app como uma imagem nativa, podemos ter certeza de que nosso aplicativo está fazendo um excelente uso dos custos de infraestrutura durante a execução.
Compare os tempos de inicialização do nosso app de linha de base com esse novo nativo.

7. Resumo/revisão dos dados
Parabéns por criar e implantar um app nativo Spring no Google Cloud.
Esperamos que este tutorial incentive você a se familiarizar com o projeto Spring Native e a considerá-lo caso ele atenda às suas necessidades no futuro.
Opcional: liberar espaço e/ou desativar o serviço
Se você criou um projeto na nuvem do Google Cloud para este codelab ou está reutilizando um projeto atual, evite cobranças desnecessárias dos recursos que usamos.
É possível excluir ou desativar os serviços do Cloud Run que criamos, excluir a imagem que hospedamos ou encerrar todo o projeto.
8. Outros recursos
Embora o projeto Spring Native seja novo e experimental, já há muitos recursos úteis para ajudar os primeiros usuários a resolver problemas e participar:
Outros recursos
Confira abaixo os recursos on-line que podem ser relevantes para este tutorial:
- Saiba mais sobre as dicas nativas
- Saiba mais sobre o GraalVM
- Como participar
- Erro de falta de memória ao criar imagens nativas
- Erro de falha ao iniciar o aplicativo
Licença
Este conteúdo está sob a licença Atribuição 2.0 Genérica da Creative Commons.