Observação:
- Este tutorial está disponível em um ambiente de laboratório gratuito fornecido pela Oracle.
- Ele usa valores de exemplo para credenciais, tenancy e compartimentos do Oracle Cloud Infrastructure. Ao concluir seu laboratório, substitua esses valores por valores específicos do seu ambiente de nuvem.
Noções Básicas sobre Reflexão e Imagem Nativa do GraalVM
Introdução
Este laboratório destina-se a desenvolvedores que desejam entender mais sobre como a reflexão funciona no GraalVM Native Image.
O GraalVM Native Image permite a compilação antecipada de um aplicativo Java em um executável nativo autocontido. Com o GraalVM Native Image, somente o código exigido pelo aplicativo em tempo de execução é adicionado ao executável nativo.
Esses executáveis nativos têm várias vantagens importantes, pois eles:
- Usar uma fração dos recursos exigidos pela JVM, de modo que a execução seja mais barata
- Começa em milissegundos
- Entregue o desempenho de pico imediatamente, sem aquecimento
- Pode ser empacotado em imagens de contêiner leves para implantações mais rápidas e eficientes
- Superfície de ataque reduzida (mais sobre isso em laboratórios futuros)
Muitas das principais estruturas de microsserviços oferecem suporte à compilação antecipada com o GraalVM Native Image, incluindo Micronaut, Spring, Helidon e Quarkus.
Além disso, há plug-ins Maven e Gradle para Native Image para facilitar a criação, o teste e a execução de aplicativos Java como executáveis nativos.
Observação: O Oracle Cloud Infrastructure (OCI) fornece o GraalVM Enterprise sem custo adicional.
Tempo estimado de laboratório: 30 minutos
Objetivos do laboratório
Neste laboratório, você executará as seguintes tarefas:
- Aprender a criar código Java que usa reflexão em executáveis independentes, usando a ferramenta de criação
native-image
- Conheça as ferramentas de configuração assistidas disponíveis com o GraalVM
OBSERVAÇÃO: sempre que você vir o ícone de laptop, em algum lugar você precisará fazer algo. Cuidado com isso.
# This is where we you will need to do something
Seu ambiente de desenvolvimento é fornecido por um host remoto: uma Instância de Computação do OCI com o Oracle Linux 8, 1 CPU e 32 GB de memória.
O ambiente de desktop do Luna Labs será exibido antes que o host remoto esteja pronto, o que pode levar até dois minutos.
ETAPA 1: Conectar-se a um Host Virtual e Verificar o Ambiente de Desenvolvimento
A conexão com o host remoto é feita por meio da execução de um script de configuração no ambiente do Luna Desktop. Este script está disponível por meio da guia de recursos
-
Na área de trabalho, clique duas vezes no ícone Luna-Lab.html. A página é aberta para exibir credenciais e informações do Oracle Cloud Infrastructure específicas do seu laboratório.
-
A Guia Recursos será exibida. Observe que a engrenagem mostrada ao lado do título Recursos girará enquanto a instância de computação está sendo provisionada na nuvem teh.
-
Quando a instância for provisionada, isso poderá levar até 2 minutos; você verá o seguinte exibido na guia Recursos
-
Copie o script de configuração, que configura o ambiente do VS Code, na guia de recursos. Clique no link Exibir Detalhes para revelar a configuração. Copie isso conforme mostrado na captura de tela abaixo.
-
Abra um Terminal, conforme mostrado na captura de tela abaixo:
-
Cole o código de configuração no terminal, que abrirá o VS Code para você.
E você acabou! Parabéns! Agora você foi conectado com sucesso a um host remoto no Oracle Cloud!
ETAPA 2: A Premissa Mundo Fechada
A criação de um executável independente com a ferramenta native-image
que vem com o GraalVM é um pouco diferente da criação de aplicativos Java. A Imagem Nativa usa o que é conhecido como pressuposto do Mundo Fechado.
A suposição Mundo Fechado significa que todo o código de bytes no aplicativo que pode ser chamado no runtime deve ser conhecido (observado e analisado) no tempo de construção, ou seja, quando a ferramenta native-image
estiver criando o executável independente.
Antes de continuar, vale a pena passar o modelo de criação/execução de aplicativos criados com o GraalVM Native Image.
- Compile seu código-fonte Java em classes de código de byte Java
- Usando a ferramenta
native-image
, crie essas classes de código de byte Java em um executável nativo - Executar o executável nativo
Mas o que realmente acontece durante o passo 2?
Primeiramente, a ferramenta native-image
executa uma análise para ver quais classes em seu aplicativo podem ser acessadas. Vamos analisar isso em mais detalhes em breve.
Em segundo lugar, as classes encontradas, que são conhecidas como seguras para serem inicializadas (Inicialização Automática de Classes Seguras), são inicializadas. Os dados de classe das classes inicializadas são carregados no heap de imagem que, por sua vez, é salvo em executável independente (na seção de texto). Este é um dos recursos da ferramenta native-image
do GraalVM que pode ser criada para aplicativos de início rápido.
OBSERVAÇÃO: : Não é o mesmo que a inicialização do objeto. A inicialização do objeto ocorre durante o tempo de execução do executável nativo.
Dissemos que voltaríamos ao tema da acessibilidade. Como foi mencionado anteriormente, a análise determina quais classes, métodos e campos precisam ser incluídos no executável independente. A análise é estática, ou seja, não executa o código. A análise pode determinar algum caso de carregamento dinâmico de classe e usos de reflexão (veja ), mas há casos que não será capaz de pegar.
Para lidar com os recursos dinâmicos do Java, a análise precisa ser informada sobre quais classes usam reflexão ou quais classes são carregadas dinamicamente.
Vamos ver um exemplo.
STEP 3: Um Exemplo Usando Reflexão
Imagine que você tenha a seguinte classe: ReflectionExample.java
(uma cópia disso pode ser encontrada no diretório, demo/ReflectionExample.java
):
import java.lang.reflect.Method;
class StringReverser {
static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
class StringCapitalizer {
static String capitalize(String input) {
return input.toUpperCase();
}
}
public class ReflectionExample {
public static void main(String[] args) throws ReflectiveOperationException {
String className = args[0];
String methodName = args[1];
String input = args[2];
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName, String.class);
Object result = method.invoke(null, input);
System.out.println(result);
}
}
Primeiro, crie um Terminal dentro do VS Code. Isso é feito em Terminal > Novo terminal
Em seguida, vamos criar o código. No shell, a partir de dentro do Código VS, execute o seguinte comando:
javac ReflectionExample.java
O método principal na classe ReflectionExample
carrega uma classe cujo nome foi passado como um argumento, um caso de uso muito dinâmico! O segundo argumento para a classe é o nome do método na classe carregada dinamicamente que deve ser chamada.
Vamos executá-lo e ver o que ele faz.
java ReflectionExample StringReverser reverse "hello"
Como esperado, o método reverse
na classe StringReverser
foi encontrado, via reflexão. O método foi invocado e reverteu nossa String de entrada de "hello". Até agora, tão bom.
OK, mas o que acontece se tentarmos criar uma imagem nativa fora do programa? Vamos experimentar. No shell, execute o seguinte comando:
native-image --no-fallback ReflectionExample
OBSERVAÇÃO: a opção
--no-fallback
paranative-image
fará com que o build falhe se não for possível criar um executbalo nativo autônomo.
Agora, vamos executar o executável nativo gerado e ver o que ele faz:
./reflectionexample StringReverser reverse "hello"
Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.Class.forName(DynamicHub.java:1214)
at ReflectionExample.main(ReflectionExample.java:21)
O que aconteceu aqui? Parece que nosso executável nativo não conseguiu encontrar a classe, StringReverser
. Como isso aconteceu? Até agora, acho que provavelmente temos uma ideia do porquê. A premissa do Mundo Fechado.
Durante a análise executada pela ferramenta native-image
, não foi possível determinar se a classe StringReverser
já foi usada. Portanto, ele removeu a classe do executável nativo que gerou. Observação: ao remover classes indesejadas do executável independente, a ferramenta para reduzir o código que é criado incluindo apenas classes que são conhecidas por serem usadas. Como acabamos de ver, isso pode acalmar questões com reflexão, mas, felizmente, há uma maneira de lidar com isso.
ETAPA 4: Introdução à Configuração de Reflexão de Imagem Nativa
Podemos dizer à ferramenta de criação native-image
sobre instâncias de reflexão por meio de arquivos de configuração especiais. Esses arquivos são gravados em JSON
e podem ser passados para a ferramenta native-image
por meio do uso de sinalizadores.
Então, quais outros tipos de informações de configuração podemos passar para a ferramenta de criação native-image
? No momento, as ferramentas suportam a leitura de arquivos que contêm detalhes sobre:
- Reflexão
- Recursos - arquivos de recursos que serão exigidos pelo aplicativo
- JNI
- Proxies Dinâmicos
- Serialização
Estamos apenas olhando para como lidar com a reflexão neste laboratório, então vamos nos concentrar nisso.
Veja a seguir um exemplo da aparência desses arquivos (parado aqui):
[
{
"name" : "java.lang.Class",
"queryAllDeclaredConstructors" : true,
"queryAllPublicConstructors" : true,
"queryAllDeclaredMethods" : true,
"queryAllPublicMethods" : true,
"allDeclaredClasses" : true,
"allPublicClasses" : true
},
{
"name" : "java.lang.String",
"fields" : [
{ "name" : "value" },
{ "name" : "hash" }
],
"methods" : [
{ "name" : "<init>", "parameterTypes" : [] },
{ "name" : "<init>", "parameterTypes" : ["char[]"] },
{ "name" : "charAt" },
{ "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
]
},
{
"name" : "java.lang.String$CaseInsensitiveComparator",
"queriedMethods" : [
{ "name" : "compare" }
]
}
]
Com base nisso, podemos ver que classes e métodos acessados por meio da API de Reflexão precisam ser configurados. Podemos fazer isso manualmente, mas a maneira mais conveniente de gerar esses arquivos de configuração é por meio do uso da configuração assistida javaagent
.
ETAPA 5: Imagem Nativa, Configuração Assistida: Informe o Agente Java
É certamente possível gravar um arquivo de configuração de reflexão completo do zero, mas o runtime GraalVM Java fornece um agente de rastreamento java, o javaagent
, que gerará isso para você automaticamente quando você executar seu aplicativo.
Vamos tentar isso.
Execute o aplicativo com o agente de rastreamento ativado. Em nosso shell, execute o seguinte:
# Note: the tracing agent parameter must come before the classpath and jar params on the command ine
java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
Vamos observar a configuração criada:
cat META-INF/native-image/reflect-config.json
[
{
"name":"StringReverser",
"methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
}
]
Você pode executar esse processo várias vezes e as execuções serão mescladas se especificar native-image-agent=config-merge-dir
, como é mostrado no exemplo abaixo:
java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
A criação do executável independente agora usará a configuração fornecida. Vamos criá-lo:
native-image --no-fallback ReflectionExample
E vamos ver se funciona melhor:
./reflectionexample StringReverser reverse "hello"
Sim!
Conclusões
A criação de executáveis independentes com o GraalVM Native Image depende da suposição de Mundo Fechado, ou seja, precisamos saber com antecedência, ao criar executáveis independentes, sobre quaisquer casos de reflexão que possam ocorrer em nosso código.
A plataforma GraalVM fornece uma maneira de especificar, para a ferramenta de compilação native-image
, quando a reflexão é usada. Observação: Para alguns casos simples, a ferramenta native-image
pode descobrir esses casos para si mesma.
A plataforma GraalVM também fornece uma maneira de descobrir usos de reflexão e outros comportamentos dinâmicos por meio do agente de Rastreamento Java e pode gerar automaticamente os arquivos de configuração necessários à ferramenta native-image
.
Há algumas coisas que você deve ter em mente ao usar o agente de rastreamento:
- Use suas suítes de teste. Você precisa exercitar quantos caminhos no código puder
- Talvez seja necessário revisar e editar seus arquivos de configuração
Esperamos que você tenha gostado deste tutorial e tenha aprendido algo sobre como podemos lidar com a reflexão ao usar o Native Image.
Saiba Mais
- Assista a uma apresentação do arquiteto Native Image Christian Wimmer GraalVM Native Image: Análise estática em larga escala para Java
- Documentação de referência do GraalVM EE Native Image
- Uso de Reflexão em Imagens Nativas
- Inicialização de Classe no Native Image
- Configuração com Assistência com o Agente de Rastreamento
Mais Recursos de Aprendizagem
Explore outros laboratórios em docs.oracle.com/learn ou acesse mais conteúdo de aprendizado gratuito no canal YouTube do Oracle Learning. Além disso, visite education.oracle.com/learning-explorer para se tornar um Oracle Learning Explorer.
Para obter a documentação do produto, visite o Oracle Help Center.
Understanding Reflection and GraalVM Native Image
F54906-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.