W3docs

Migration vers les modules Java

Stratégies pour migrer des applications Java basées sur le classpath vers le Java Platform Module System.

Vous n'êtes pas obligé de modulariser pour passer à Java 9+. Une application classpath s'exécute sans modification — tout devient un grand module sans nom. La migration est une étape facultative que vous effectuez lorsque vous souhaitez une encapsulation forte, un runtime personnalisé jlink, ou un graphe de dépendances plus propre. L'art consiste à le faire de manière incrémentale, car les trois types de modules — nommé, automatique et sans nom — permettent la coexistence de code modulaire et non modulaire.

Ce chapitre couvre les cas où la migration vaut la peine, comment faire fonctionner une application existante sur un JDK moderne en premier, les deux directions de modularisation possibles (bottom-up vs. top-down), les outils qui font le gros du travail, et un exemple exécutable qui inspecte le graphe de modules comme le fait jdeps. Si les termes employés ici ne vous sont pas familiers, commencez par l'introduction aux modules et la déclaration d'un module.

Première étape : exécuter simplement sur Java 9+ (sans modules)

Avant d'ajouter un quelconque module-info.java, recompilez et exécutez votre application existante sur le nouveau JDK avec tout sur le classpath. Les problèmes probables n'ont rien à voir avec vos modules et tout à voir avec le JDK désormais modulaire :

  • Modules Java EE supprimésjava.xml.bind (JAXB), java.activation, CORBA, etc. ont été supprimés du JDK. Ajoutez-les à nouveau en tant que dépendances ordinaires.
  • Accès aux internals encapsuléssun.misc.Unsafe et ses équivalents ne sont plus accessibles. Les options --add-exports / --add-opens constituent une échappatoire pendant que vous supprimez leur utilisation.
  • Packages partagés — deux JARs contribuant au même package entrent en conflit sur le module path (mais pas sur le classpath).

Faites d'abord fonctionner l'application sur le classpath. Seulement ensuite, commencez la modularisation.

Migration bottom-up

La stratégie préférée lorsque vous contrôlez l'intégralité du code source :

  1. Commencez par les bibliothèques feuilles — les modules qui ne dépendent d'aucun de vos modules.
  2. Donnez à chacun un module-info.java et déplacez-le sur le module path.
  3. Leurs dépendants peuvent maintenant les requires. Remontez vers le haut de l'arbre de dépendances jusqu'au point d'entrée de l'application.

Cela fonctionne parce qu'un module nommé peut exiger d'autres modules nommés et des modules automatiques — donc tant que les propres dépendances d'une feuille sont au moins automatiques, vous pouvez la modulariser. Chaque étape maintient le build au vert. Si une feuille expose un comportement enfichable, c'est aussi le moment naturel d'introduire une frontière de services pour que ses dépendants uses une interface plutôt qu'une classe concrète.

Migration top-down

Lorsque vous ne contrôlez pas les bibliothèques (JARs tiers sans descripteurs), procédez à l'inverse :

  1. Modularisez d'abord votre propre code de haut niveau.
  2. Placez les JARs tiers encore non modulaires sur le module path, où ils deviennent des modules automatiques.
  3. Faites-les requires par leur nom automatique (issu du nom de fichier JAR ou de Automatic-Module-Name).
  4. Lorsqu'une bibliothèque livre un vrai module-info, remplacez la dépendance automatique par la nommée — sans changer votre requires.

Les modules automatiques sont l'échafaudage qui rend le top-down possible ; ils permettent à votre code nommé de dépendre de JARs qui ne sont pas encore modulaires.

Outils et pièges

  • jdeps analyse les dépendances réelles d'un JAR et peut même générer un premier module-info.java (jdeps --generate-module-info). Commencez par là plutôt que d'écrire les directives à la main.
  • Automatic-Module-Name — si vous publiez une bibliothèque, ajoutez cette entrée de manifeste avant d'écrire un descripteur complet. Cela fixe un nom de module stable pour que les utilisateurs de modules automatiques en aval ne soient pas cassés quand vous renommerez plus tard le JAR.
  • Les packages partagés doivent être fusionnés. Le module path interdit à deux modules de posséder le même package ; le classpath le tolérait. C'est le blocage de migration le plus courant.
  • opens pour la réflexion. Les frameworks qui font de la réflexion sur vos classes (Jackson, JPA, Spring) ont besoin de opens, ou vous obtiendrez une InaccessibleObjectException à l'exécution même si la compilation a réussi.
Avertissement

Les packages partagés sont l'échec qui surprend le plus les gens. Sur le classpath, deux JARs contenant chacun des classes dans com.example.util fusionnaient simplement ; sur le module path, le résolveur les rejette avec une erreur "module reads package ... from both". Aucun flag ne rend cela légal — vous devez fusionner le package dupliqué dans un seul module (ou en renommer un). Auditez les packages partagés avec jdeps avant de commencer à écrire des descripteurs.

Un exemple pratique : inspecter un graphe de dépendances comme jdeps

La planification de la migration commence par « qui dépend de quoi ». Ce programme lit les descripteurs de modules de la couche de démarrage à l'exécution — les mêmes informations requires que jdeps expose — et détecte également si lui-même s'exécute en tant que module nommé ou sur le classpath, la vérification même qui indique jusqu'où une migration a progressé.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Le programme a indiqué qu'il s'exécutait en tant que module UNNAMED — exactement ce que l'on attend du code classpath, et le signal d'exécution que ce code source n'a pas encore été modularisé. Réexécuter après avoir ajouté un module-info et migré vers le module path ferait passer ceci à NAMED, vous donnant une vérification concrète de « sommes-nous arrivés ? » pendant la migration.
  • inspect("java.sql") a affiché sa vraie liste requires, y compris une dépendance transitive de style java.transaction.xa ou java.logging. C'est la même information que jdeps expose — connaître les vraies dépendances d'un module est la première étape pour écrire son module-info.java, et le descripteur les contient déjà.
  • Certains requires affichaient (transitive). Ce sont les dépendances qu'un module ré-exporte ; quand vous faites requires java.sql, vous lisez automatiquement aussi ses dépendances transitives. Les repérer vous indique quelles lignes requires transitive copier quand vous modularisez du code qui enveloppe un tel module.
  • findModule a retourné un Optional, rappelant qu'un module peut simplement ne pas être présent dans un runtime donné — une image taillée avec jlink pourrait omettre entièrement java.desktop. Les plans de migration doivent tenir compte des modules qui figurent réellement dans le runtime cible.
  • Chaque module dépend finalement de java.base, et il n'apparaît jamais dans une liste requires parce qu'il est implicite. Le nombre total de modules au démarrage montre que le JDK est un graphe que vous pouvez interroger par programmation — la fondation sur laquelle des outils comme jdeps et jlink s'appuient pour analyser et réduire une application.

Un ordre de migration sensé, résumé

  1. Exécuter sur le classpath du nouveau JDK ; corriger les problèmes liés aux modules supprimés et aux APIs internes.
  2. Utiliser jdeps pour cartographier les dépendances et trouver les packages partagés.
  3. Ajouter Automatic-Module-Name à toute bibliothèque que vous publiez.
  4. Modulariser bottom-up si vous possédez l'arbre, top-down (en s'appuyant sur les modules automatiques) sinon.
  5. Ajouter opens là où les frameworks font de la réflexion ; vérifier à l'exécution, pas seulement à la compilation.

Cela complète la partie Java Platform Module System : vous savez maintenant ce que sont les modules, comment en déclarer un, les trois types et comment ils coexistent, comment les services découplent les modules, et comment migrer une application existante vers le module path sans réécriture.

Pratique

Pratique
Votre équipe possède l'intégralité du code source d'une application et souhaite une encapsulation forte. La bibliothèque A ne dépend d'aucun de vos modules ; la bibliothèque B dépend de A ; l'exécutable dépend de B. Quel ordre de migration maintient chaque build intermédiaire au vert avec le moins de contournements par modules automatiques ?
Votre équipe possède l'intégralité du code source d'une application et souhaite une encapsulation forte. La bibliothèque A ne dépend d'aucun de vos modules ; la bibliothèque B dépend de A ; l'exécutable dépend de B. Quel ordre de migration maintient chaque build intermédiaire au vert avec le moins de contournements par modules automatiques ?
Was this page helpful?