Remarque :

Présentation de Reflection et de GraalVM Native Image

Introduction

Cet atelier est destiné aux développeurs qui souhaitent en savoir plus sur le fonctionnement de la réflexion au sein de GraalVM Native Image.

GraalVM Native Image permet la compilation anticipée d'une application Java dans un exécutable natif autonome. Avec GraalVM Native Image, seul le code requis par l'application au moment de l'exécution est ajouté à l'exécutable natif.

Ces exécutables natifs présentent un certain nombre d'avantages importants, en ce qu'ils :

Nombre des principaux cadres de travail de microservices prennent en charge la compilation anticipée avec GraalVM Native Image, y compris Micronaut, Spring, Helidon et Quarkus.

De plus, il existe des modules d'extension Maven et Gradle pour Native Image afin de faciliter la création, le test et l'exécution d'applications Java en tant qu'exécutables natifs.

Remarque : Oracle Cloud Infrastructure (OCI) fournit GraalVM Enterprise sans frais supplémentaires.

Temps de laboratoire estimé : 30 minutes

Objectifs du laboratoire

Dans cet exercice, vous allez effectuer les tâches suivantes :

REMARQUE : A chaque fois que vous voyez l'icône de l'ordinateur portable, vous devrez faire quelque chose. Faisons attention à eux.

# This is where we you will need to do something

Votre environnement de développement est fourni par un hôte distant : une instance de calcul OCI avec Oracle Linux 8, 1 UC et 32 Go de mémoire.
L'environnement de bureau Luna Labs s'affichera avant que l'hôte distant soit prêt, ce qui peut prendre jusqu'à deux minutes.

Etape 1 : connexion à un hôte virtuel et vérification de l'environnement de développement

La connexion à votre hôte distant s'effectue via l'exécution d'un script de configuration dans votre environnement Luna Desktop. Ce script est disponible via l'onglet Ressources

  1. Dans le bureau, cliquez deux fois sur l'icône Luna-Lab.html. La page apparaît pour afficher les informations d'identification et les informations d'identification Oracle Cloud Infrastructure propres à l'exercice.

  2. L'onglet Ressources s'affiche. Le graphique affiché en regard du titre Ressources tourne tandis que l'instance de calcul est provisionnée dans le cloud de thé.

  3. Une fois l'instance provisionnée, cette opération peut prendre jusqu'à 2 minutes. Les éléments suivants apparaissent dans l'onglet Ressources.

    Luna - Onglet Ressources

  4. Copiez le script de configuration, qui configure votre environnement VS Code, à partir de l'onglet Ressources. Cliquez sur le lien Visualiser les détails pour afficher la configuration. Copiez-le comme indiqué dans la capture d'écran ci-dessous.

    Script de configuration de copie

  5. Ouvrez un terminal, comme indiqué dans la capture d'écran ci-dessous :

    Ouvrir le terminal

  6. Collez le code de configuration dans le terminal, qui va ouvrir VS Code pour vous.

    Coller le terminal 1

    Coller le terminal 2

Et tu as fini ! Félicitations. Vous êtes maintenant connecté à un hôte distant dans Oracle Cloud !

Etape 2 : hypothèse mondiale fermée

La création d'un exécutable autonome avec l'outil native-image fourni avec GraalVM est un peu différente de la création d'applications Java. Native Image utilise l'hypothèse nommée Close World.

L'hypothèse de monde fermé signifie que tout le code exécutable de l'application qui peut être appelé lors de l'exécution doit être connu (observé et analysé) au moment de la construction, c'est-à-dire lorsque l'outil native-image construit l'exécutable autonome.

Avant de continuer, il est utile de passer en revue le modèle de création/d'exécution pour les applications créées avec GraalVM Native Image.

  1. Compiler le code source Java dans des classes de code Java d'octets
  2. A l'aide de l'outil native-image, créez ces classes de code d'octet Java dans un exécutable natif
  3. Exécuter l'exécutable natif

Mais que se passe-t-il réellement à l'étape 2 ?

Tout d'abord, l'outil native-image effectue une analyse pour voir quelles classes de votre application sont accessibles. Nous étudierons cela plus en détail prochainement.

Ensuite, les classes trouvées, connues pour être sûres à initialiser (Automatic Initialization of Safe Classes), sont initialisées. Les données de classe des classes initialisées sont chargées dans la portion de mémoire de l'image qui, à son tour, est enregistrée dans un exécutable autonome (dans la section de texte). Il s'agit de l'une des fonctionnalités de l'outil GraalVM native-image pouvant être utilisées pour des applications de démarrage rapide.

REMARQUE : Ce n'est pas le même que l'initialisation d'objet. L'initialisation d'objet se produit lors de l'exécution de l'exécutable natif.

Nous avons dit que nous reviendrions au sujet de l'accessibilité. Comme mentionné précédemment, l'analyse détermine les classes, méthodes et champs à inclure dans l'exécutable autonome. L'analyse est statique, c'est-à-dire qu'elle n'exécute pas le code. L'analyse peut déterminer un cas de chargement dynamique de classes et d'utilisation de la réflexion (voir ), mais il arrive qu'elle ne puisse pas reprendre.

Pour gérer les fonctionnalités dynamiques de Java, l'analyse doit savoir quelles classes utilisent la réflexion ou quelles classes sont chargées dynamiquement.

Etudions un exemple.

Etape 3 : exemple avec la réflexion

Supposons que vous disposiez de la classe suivante : ReflectionExample.java (une copie de cette classe se trouve dans le répertoire, 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);
    }
}

Tout d'abord, créez un terminal dans le code VS. Ceci est fait depuis, Terminal > New Terminal

Ensuite, nous allons créer le code. Dans votre shell, à partir du code VS, exécutez la commande suivante :

javac ReflectionExample.java

La méthode principale de la classe ReflectionExample charge une classe dont le nom a été transmis en tant qu'argument, un cas d'utilisation très dynamique. Le deuxième argument de la classe est le nom de la méthode de la classe chargée dynamiquement qui doit être appelée.

Exécutez-la et voyons ce qu'elle fait.

java ReflectionExample StringReverser reverse "hello"

Comme prévu, la méthode reverse de la classe StringReverser a été trouvée, par réflexion. La méthode a été appelée et a inversé la chaîne d'entrée "hello". Jusqu'à présent, tant mieux.

OK, mais que se passe-t-il si nous essayons de construire une image native hors programme ? Essayons. Dans votre shell, exécutez la commande suivante :

native-image --no-fallback ReflectionExample

REMARQUE : L'option --no-fallback sur native-image provoque l'échec de la création si elle ne peut pas créer un exécutable natif autonome.

Exécutez à présent l'exécutable natif généré et voyons ce qu'il fait :

./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)

Qu'est-il arrivé ici ? Il semble que notre exécutable natif n'a pas pu trouver la classe StringReverser. Comment cela s'est-il produit ? À l'heure actuelle, je pense que nous avons probablement une idée pourquoi. L'hypothèse du monde fermé.

Lors de l'analyse effectuée par l'outil native-image, il n'a pas été en mesure de déterminer que la classe StringReverser a déjà été utilisée. Elle a donc supprimé la classe de l'exécutable natif qu'elle a généré. Remarque : en supprimant les classes indésirables de l'exécutable autonome, l'outil réduit le code créé en incluant uniquement les classes connues pour être utilisées. Comme nous venons de le voir, cela peut mettre en évidence des problèmes de réflexion, mais heureusement, il y a un moyen de traiter cela.

Etape 4 : présentation de la configuration de la réflexion de l'image native

Nous pouvons indiquer à l'outil de création native-image les instances de réflexion via des fichiers de configuration spéciaux. Ces fichiers sont écrits dans JSON et peuvent être transmis à l'outil native-image via l'utilisation d'indicateurs.

Quels autres types d'informations de configuration pouvons-nous transmettre à l'outil de création native-image ? L'outil prend actuellement en charge la lecture de fichiers contenant des détails sur :

Nous ne cherchons qu'à faire face à la réflexion dans cet exercice, c'est pourquoi nous allons nous concentrer sur ce point.

Voici un exemple de l'apparence de ces fichiers (pris à partir de ici) :

[
  {
    "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" }
    ]
  }
]

De ce fait, nous pouvons constater que les classes et les méthodes accessibles via l'API Réflexion doivent être configurées. Nous pouvons le faire à la main, mais le moyen le plus pratique de générer ces fichiers de configuration consiste à utiliser la configuration assistée javaagent.

Etape 5 : image native, configuration assistée : entrez l'agent Java

Il est certainement possible d'écrire un fichier de configuration de réflexion complet à partir de zéro, mais l'exécution Java GraalVM fournit un agent de trace java, javaagent, qui le génère automatiquement lorsque vous exécutez votre application.

Essayons ça.

Exécutez l'application avec l'agent de suivi activé. Dans notre shell, exécutez les commandes suivantes :

# 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"

Configuration de l'agent de trace

Examinons la configuration créée :

cat META-INF/native-image/reflect-config.json
[
    {
    "name":"StringReverser",
    "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
    }
]

Vous pouvez exécuter ce processus plusieurs fois et les exécutions sont fusionnées si vous indiquez native-image-agent=config-merge-dir, comme illustré dans l'exemple ci-dessous :

java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"

La création de l'exécutable autonome utilise désormais la configuration fournie. Créons-le :

native-image --no-fallback ReflectionExample

Voyons si cela fonctionne mieux :

./reflectionexample StringReverser reverse "hello"

Ça fait !

Conclusions

La création d'exécutables autonomes avec GraalVM Native Image s'appuie sur l'hypothèse Close World, à savoir qu'à l'avance, lorsque nous créons des exécutables autonomes, nous devons connaître tous les cas de réflexion pouvant survenir dans notre code.

La plate-forme GraalVM permet de spécifier, pour l'outil de création native-image, lorsque la réflexion est utilisée. Remarque : pour certains cas simples, l'outil native-image peut les repérer pour lui-même.

La plate-forme GraalVM permet également de découvrir les utilisations de la réflexion et d'autres comportements dynamiques via l'agent Java Tracing et peut générer automatiquement les fichiers de configuration requis par l'outil native-image.

Vous devez garder à l'esprit quelques points lorsque vous utilisez l'agent de suivi :

Nous espérons que vous avez apprécié ce tutoriel et que vous avez appris comment gérer la réflexion avec Native Image.

En savoir plus

Ressources de formation supplémentaires

Explorez d'autres exercices sur docs.oracle.com/learn ou accédez à davantage de contenu d'apprentissage gratuit sur le canal Oracle Learning YouTube. De plus, visitez le site education.oracle.com/learning-explorer pour devenir Oracle Learning Explorer.

Pour consulter la documentation du produit, consultez le centre d'aide Oracle.