Observação:

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:

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:

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

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

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

  3. Quando a instância for provisionada, isso poderá levar até 2 minutos; você verá o seguinte exibido na guia Recursos

    Guia Recursos Luna

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

    Copiar Script de Configuração

  5. Abra um Terminal, conforme mostrado na captura de tela abaixo:

    Abrir Terminal

  6. Cole o código de configuração no terminal, que abrirá o VS Code para você.

    Colar Terminal 1

    Colar Terminal 2

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.

  1. Compile seu código-fonte Java em classes de código de byte Java
  2. Usando a ferramenta native-image, crie essas classes de código de byte Java em um executável nativo
  3. 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 para native-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:

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"

Configuração do Agente de Rastreamento

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:

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

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.