Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Java Spring
  3. Les images natives de GraalVM
Extrait - Java Spring Le socle technique des applications Jakarta EE (5e édition)
Extraits du livre
Java Spring Le socle technique des applications Jakarta EE (5e édition) Revenir à la page d'achat du livre

Les images natives de GraalVM

Introduction

GraalVM se distingue comme une innovation technologique significative au sein de l’écosystème du développement logiciel, se positionnant aux côtés d’autres avancées majeures telles que Spring et la programmation réactive, mais aussi aux côtés de technologies disruptives comme les conteneurs Docker, qui ont révolutionné l’isolation et le déploiement des applications, Kubernetes pour l’orchestration de conteneurs à grande échelle, et les microservices qui ont transformé l’architecture des applications en permettant des développements plus modulaires et agiles. Le développement dirigé par les événements (Event-Driven Development) et l’informatique sans serveur (serverless computing) sont également des innovations qui ont remodelé les paradigmes de programmation et d’infrastructure, en offrant des moyens plus efficaces de construire et de déployer des applications. Ensemble, ces technologies ont façonné l’avenir du développement logiciel, en ouvrant la voie à des architectures plus flexibles, performantes et évolutives.

GraalVM est une technologie polyglotte de machine virtuelle (VM) avancée développée par Oracle Labs. Sa particularité réside dans sa capacité à exécuter...

Images natives de GraalVM

Les images natives de GraalVM révolutionnent le déploiement et l’exécution des applications Java. Ce sont des exécutables autonomes créés par traitement anticipé des applications Java compilées. Elles se distinguent par une empreinte mémoire réduite et un démarrage plus rapide par rapport aux applications sur JVM (Java Virtual Machine).

Ces images natives sont idéales pour les applications déployées via des images de conteneur et s’intègrent parfaitement aux plateformes de type fonction en tant que service (FaaS). À la différence des applications Java traditionnelles, celles basées sur GraalVM Native Image nécessitent un traitement préalable pour générer un exécutable. Ce processus inclut une analyse statique du code de l’application à partir de son point d’entrée principal.

Une image native GraalVM est un exécutable spécifique à une plateforme, qui ne requiert pas l’installation d’une JVM pour son exécution.

Les images natives de GraalVM, étant produites en amont, présentent des caractéristiques distinctives par rapport aux applications JVM, notamment :

  • Analyse statique de l’application lors de sa construction, à partir de son point d’entrée principal.

  • Suppression...

Ahead Of Time (AOT)

Les applications Spring Boot typiques présentent une dynamique importante, et leur configuration est habituellement réalisée lors de l’exécution. Cette dynamique repose en grande partie sur la capacité à réagir en temps réel aux conditions d’exécution pour assurer une configuration appropriée, ce qui est au cœur de l’autoconfiguration de Spring Boot.

Cependant, informer GraalVM de ces aspects dynamiques pourrait compromettre les avantages liés à l’analyse statique. Par conséquent, lors de la création d’images natives avec Spring Boot, on part du principe que le contexte d’exécution est un « monde fermé », où les aspects dynamiques de l’application sont limités. Dans un tel environnement, les contraintes imposées par GraalVM sont accompagnées de restrictions supplémentaires :

  • Les beans définis dans votre application ne peuvent pas être modifiés durant l’exécution. Cela implique certaines limitations, notamment :

  • L’annotation @Profile de Spring et les configurations spécifiques aux profils sont soumises à des restrictions.

  • Les propriétés influençant la création d’un bean ne sont pas prises en charge (comme @ConditionalOnProperty et les propriétés .enable).

Avec ces restrictions en place, Spring peut procéder à un traitement préalable (AOT) durant la phase de construction et générer des ressources supplémentaires utilisables par GraalVM.

La compilation AOT de GraalVM permet de compiler le code source en exécutables natifs avant l’exécution, améliorant ainsi les performances au démarrage et réduisant la consommation de mémoire des applications.

Une application traitée par Spring AOT va typiquement engendrer :

  • Du code source Java.

  • Du bytecode (par exemple pour les proxies dynamiques).

  • Des fichiers de configuration JSON pour GraalVM, incluant :

  • des indications sur les ressources (resource-config.json) ;

  • des indications pour la réflexion (reflect-config.json) ;

  • des indications pour la sérialisation (serialization-config.json) ;

  • des indications pour les proxies Java...

Application optimisée pour le mode natif

GraalVM présente certaines limitations et considérations spécifiques lors de la conception d’une application Spring Boot. Parmi les principales contraintes figurent les problématiques liées à la réflexion et à la génération dynamique de code, car GraalVM exige une connaissance préalable et statique du code pour la compilation en natif. Pour intégrer cette limitation dans la conception d’une application Spring Boot, voici un exemple :

Lorsque vous utilisez des fonctionnalités qui s’appuient sur la réflexion, comme l’injection de dépendances via @Autowired ou l’accès à des champs de manière dynamique, il est crucial de fournir à GraalVM des configurations explicites. Ces configurations permettent à GraalVM de comprendre et d’inclure les parties nécessaires du code lors de la compilation native. Une approche consiste à utiliser le fichier reflect-config.json pour lister explicitement les classes, méthodes, et champs auxquels GraalVM doit accéder par réflexion.

  { 
    "name" : "com.votreapplication.VotreClasse", 
    "allDeclaredConstructors" : true, 
    "allPublicConstructors" : true, 
    "allDeclaredMethods" : true, 
    "allDeclaredFields" : true 
  } 

Ce fichier doit être placé dans le répertoire META-INF/native-image du projet, permettant ainsi à GraalVM de préparer l’image native en tenant compte de ces éléments.

En adoptant cette stratégie, les développeurs peuvent surmonter une des limitations clés de GraalVM lors de l’utilisation de Spring Boot, assurant que l’application compilée nativement se comporte comme prévu, sans omettre des parties critiques du code nécessaires à son exécution. Cette méthode exige une compréhension précise des parties du code qui utilisent la réflexion, soulignant l’importance d’une planification et d’une documentation adéquates lors de la conception d’applications...

Exemple d’application

Voici un exemple d’application sur lequel on se basera pour créer une image native :

@RestController 
@SpringBootApplication 
public class MonApplication { 
 
  @RequestMapping("/") 
  String accueil() { 
    return "Bonjour à vous!"; 
  } 
 
  public static void main(String[] args) { 
    SpringApplication.run(MonApplication.class, args); 
  } 
 
} 

Cette application est basée sur Spring MVC et utilise un Tomcat intégré. Ces deux composants ont été testés et sont confirmés comme étant compatibles avec la création d’images natives via GraalVM.

Pour créer un exécutable natif (sans passer par Docker), les outils de build natifs de GraalVM sont une option efficace. Disponibles pour Maven et Gradle, ils offrent diverses fonctionnalités liées à GraalVM, dont la création d’images natives.

Pour générer une image native, il est nécessaire d’avoir GraalVM sur votre machine. 

Vous pouvez installer GraalVM (https://www.graalvm.org/latest/docs/getting-started/) et/ou Liberica Native Image Kit (https://bell-sw.com/liberica-native-image-kit/ version 22.3), ainsi que les outils de build...

Conteneurisation de l’exemple

L’utilisation de Docker facilite grandement les choses. Spring Boot facilite la création d’images natives en intégrant le support des buildpacks pour Maven et Gradle. Cela permet de générer rapidement une image optimisée dans Docker local via une simple commande. Ces images ne comprennent pas de JVM, car elles sont compilées statiquement, ce qui les rend plus légères.

Le builder utilisé est paketobuildpacks/builder-jammy-tiny:latest, caractérisé par sa légèreté et une surface d’attaque minimale. Le dépôt GitHub est ici : https://github.com/paketo-buildpacks/builder-jammy-tiny.

Alternativement, il est possible d’utiliser paketobuildpacks/builder-jammy-base:latest (https://github.com/paketo-buildpacks/builder-jammy-base) ou paketobuildpacks/builder-jammy-full:latest (https://github.com/paketo-buildpacks/builder-jammy-full) pour disposer d’outils supplémentaires dans l’image, si nécessaire.

Il est nécessaire d’avoir Docker installé. Pour les utilisateurs Linux, Docker doit être configuré pour un usage par des utilisateurs non-root.

Pour le développement d’applications avancées, et notamment pour les applications GraalVM, il est conseillé d’investir dans une machine puissante et/ou un serveur PIC (plateforme d’intégration...

Support natif et tests

1. Tests avec la JVM

Pour le développement d’applications en image native, il est conseillé de privilégier l’utilisation de la JVM pour la majorité des tests unitaires et d’intégration. Cela réduit les temps de compilation pour les développeurs et permet de bénéficier des intégrations IDE existantes. Après avoir couvert largement les tests sur la JVM, les tests spécifiques aux images natives peuvent se concentrer sur les aspects qui diffèrent probablement comme :

  • la capacité du moteur AOT de Spring à traiter votre application pour qu’elle fonctionne en mode AOT ;

  • la présence suffisante d’indices chez GraalVM pour produire une image native valide.

Une application Spring Boot détecte si elle s’exécute en tant qu’image native. Dans ce cas, elle utilise le code généré par le moteur AOT de Spring lors du build. Sur une JVM classique, ce code AOT est négligé.

Étant donné que la compilation de l’image native peut être longue, il est parfois utile de faire fonctionner votre application sur la JVM en utilisant le code d’initialisation AOT. Cela permet de vérifier rapidement l’absence d’erreurs dans le code AOT et de s’assurer que rien ne manque pour la conversion en image native....

Notions avancées

Configuration des propriétés imbriquées

Pour les propriétés de configuration, le moteur de Spring en traitement anticipé génère des indices de réflexion de manière automatique. Toutefois, pour que les propriétés de configuration imbriquées qui ne sont pas des classes internes soient reconnues, elles doivent impérativement porter l’annotation @NestedConfigurationProperty.

Voici un exemple avec la classe MesProperties :

@ConfigurationProperties(prefix = "my.properties") 
public class MesProperties { 
 
  private String name; 
 
  @NestedConfigurationProperty 
  private final Nested nested = new Nested(); 
 
  // getters / setters... 
 
} 

Puis la classe Nested correspondante :

public class Nested { 
 
  private int number; 
 
  // getters / setters... 
 
} 

Dans cet exemple, des propriétés de configuration sont créées pour my.properties.name et my.properties.nested.number. Sans l’annotation @NestedConfigurationProperty sur le champ nested, la propriété my.properties.nested.number ne serait pas accessible dans une image native.

Pour une liaison via constructeur, il est nécessaire d’annoter le champ avec @NestedConfigurationProperty :

@ConfigurationProperties(prefix = "my.properties") 
public class MonPropertiesCreator { 
 
  private final String name; 
 
  @NestedConfigurationProperty 
  private final Nested nested; 
 
  public MonPropertiesCreator(String name, Nested nested) { 
    this.name = name; 
    this.nested = nested; 
  } 
 
  // getters / setters... 
 
} 

En ce qui concerne l’utilisation de records, l’annotation @NestedConfigurationProperty doit être placée sur le paramètre :

@ConfigurationProperties(prefix = "my.properties") 
public record MyPropertiesRecord(String name,
@NestedConfigurationProperty Nested nested) { 
 
} 

Il est crucial d’utiliser des getters et setters publics dans tous les cas pour garantir que...

Pour aller plus loin

Pour approfondir vos connaissances sur le processus de traitement anticipé que les plugins de build offrent, référez-vous aux documentations spécifiques des plugins Maven et Gradle.

Pour une compréhension plus détaillée des API impliquées dans ce processus, explorez les packages org.springframework.aot.generate et org.springframework.beans.factory.aot dans les sources du framework Spring.

Pour les restrictions et problèmes éventuels liés à l’utilisation de Spring avec GraalVM, le wiki de Spring Boot est une ressource informative à consulter.

Alternatives

L’utilisation de GraalVM avec Spring Boot pour la compilation AOT et la génération d’images natives offre des avantages significatifs en termes de temps de démarrage et de consommation mémoire. Il existe des alternatives à l’utilisation de Spring avec GraalVM pour atteindre des objectifs similaires ou pour l’utiliser dans d’autres contextes.

1. Quarkus

Quarkus est un framework Java conçu pour les environnements Kubernetes et les services cloud. Il est optimisé pour les conteneurs et permet de créer des applications natives compilées avec GraalVM. Quarkus vise à offrir un temps de démarrage rapide et une faible empreinte mémoire, similaire à ce que GraalVM propose avec Spring, mais est spécifiquement conçu pour maximiser ces avantages.

Quarkus est un framework moderne conçu pour optimiser le développement d’applications Java, en particulier pour les environnements cloud, tels que Kubernetes et les architectures serverless. L’une des principales philosophies de Quarkus est de réduire autant que possible l’empreinte mémoire et d’accélérer le temps de démarrage des applications, ce qui est crucial pour les déploiements dans des environnements cloud où les ressources sont facturées à l’utilisation et où la capacité de mise à l’échelle rapide est valorisée.

Pour atteindre cet objectif, Quarkus préfère utiliser un ensemble de librairies légères et performantes, plutôt que de s’appuyer sur des frameworks plus lourds et traditionnels comme Spring. Cela ne signifie pas que Quarkus est incompatible avec l’écosystème Spring; en fait, Quarkus propose des extensions qui permettent aux développeurs familiarisés avec Spring de migrer plus facilement leurs applications ou d’utiliser des concepts similaires à ceux de Spring. Cependant, la stack principale de Quarkus est construite autour de ses propres...

Points clés

Points à retenir :

  • Les images natives de GraalVM permettent de démarrer les applications beaucoup plus rapidement.

  • Les images natives de GraalVM réduisent l’empreinte mémoire des applications.

  • Les images natives de GraalVM sont plus longues à générer que les applications classiques basées sur la JVM.

  • Il faut tester exhaustivement les images natives de GraalVM.

  • Il faut un plan B en cas de problème en production via la mise à disposition d’une version classique basée sur la JVM.