W3docs

Types de modules Java

Modules nommés, automatiques et sans nom en Java et leur interaction lors de la compilation et de l'exécution.

Le Java Platform Module System (JPMS) reconnaît trois types de modules. Un seul est le « vrai » module que vous créez ; les deux autres existent pour que les millions de JARs antérieurs à Java 9 continuent de fonctionner. Comprendre quel type adopte un JAR donné — et que cela dépend entièrement de l'endroit où vous le placez — est la clé d'une migration sans douleur. Cette page définit les trois types, montre les règles d'accès entre eux et prouve les catégories avec un programme exécutable.

Modules nommés

Un module nommé (explicite) possède un module-info.class et est placé sur le module path (--module-path / -p). C'est le citoyen à part entière :

  • Il a un nom issu de son descripteur.
  • Il lit uniquement les modules qu'il déclare avec requires.
  • Il expose uniquement les paquets qu'il déclare avec exports.

C'est le module fortement encapsulé décrit dans le chapitre sur la déclaration de module. Tout ce que JPMS promet — dépendances déclarées, internals cachés, résolution rapide en cas d'erreur — s'applique aux modules nommés.

Modules automatiques

Un module automatique est un JAR ordinaire (sans module-info) placé sur le module path. JPMS l'enveloppe dans un module afin que les modules nommés puissent le déclarer avec requires lors d'une migration — sans attendre que l'auteur de la bibliothèque ajoute un descripteur. Un module automatique :

  • Obtient un nom dérivé du nom du fichier JAR (par exemple guava-32.1.jarguava), sauf si le manifeste du JAR définit Automatic-Module-Name.
  • Exporte tous ses paquets — il n'a pas de directive exports, donc tous ses paquets sont ouverts au monde entier.
  • Lit tous les autres modules, y compris le module sans nom, afin de pouvoir encore accéder aux JARs du classpath.

C'est un pont : il vous permet de commencer à écrire des modules nommés qui dépendent de bibliothèques pas encore modularisées. Le coût est qu'il abandonne totalement l'encapsulation, et son nom dérivé automatiquement peut changer si le JAR est renommé — c'est pourquoi définir Automatic-Module-Name dans le manifeste est la chose responsable à faire pour une bibliothèque.

Le module sans nom

Le module sans nom est le filet de sécurité pour le classpath. Chaque classe chargée depuis le classpath appartient au module sans nom de son chargeur de classes. Il :

  • N'a pas de nom (getName() renvoie null, isNamed() vaut false).
  • Lit tous les autres modules du système.
  • Exporte tous ses paquets aux autres modules sans nom ou automatiques.

Mais il existe un mur délibérément unidirectionnel : un module nommé ne peut pas déclarer requires sur le module sans nom. Vous ne pouvez pas le nommer, donc vous ne pouvez pas en dépendre. C'est la règle qui impose l'ordre de migration — un module nommé ne peut dépendre que d'autres modules nommés ou automatiques, jamais de code brut sur le classpath.

La matrice d'accès

Qui peut lire qui se résume à un petit tableau :

De ↓ / Vers →NomméAutomatiqueSans nom
Nomméuniquement si requiresuniquement si requiresjamais
Automatiqueouiouioui
Sans nomouiouioui

L'unique cellule restrictive — le code nommé ne peut pas accéder au code sans nom — explique tout le principe de migration ascendante (abordé dans le chapitre suivant).

Un exemple concret : identifier le type d'un module à l'exécution

L'API Module vous indique, pour n'importe quelle classe, si son module est nommé et s'il a été synthétisé automatiquement. Ce programme inspecte trois références — sa propre classe (classpath → sans nom), un type JDK (nommé) et rapporte la couche de démarrage — pour rendre les catégories concrètes.

java— editable, runs on the server

Ce que l'on retient de l'exécution :

  • La propre classe du programme est classifiée comme UNNAMED avec un nom null, tandis que java.util.List et HttpClient sont classifiés comme NAMED (java.base, java.net.http). Exécuté depuis le classpath, votre code est toujours sans nom ; le JDK est toujours un ensemble de modules nommés. Le type d'un module est déterminé par la façon dont il a été chargé, et non par quoi que ce soit dans la classe elle-même.
  • java.base.canRead(self) a renvoyé false mais self.canRead(java.base) a renvoyé true. C'est le mur unidirectionnel en action : le module sans nom lit tout, mais aucun module nommé ne lit le module sans nom. Cette asymétrie est précisément pourquoi le code nommé ne peut pas déclarer requires sur du code classpath.
  • classify() a distingué automatique de nommé via descriptor.isAutomatic(). Vous ne verrez pas true ici (rien n'a été placé sur le module path en tant que JAR ordinaire), mais cette vérification est exactement la façon dont les outils signalent un module automatique — un vrai objet module avec un descripteur synthétisé et entièrement ouvert.
  • isExported("java.util") a renvoyé true mais isExported("jdk.internal.misc") a renvoyé false, même si les deux sont de vrais paquets à l'intérieur de java.base. Le exports d'un module nommé est une liste d'autorisation ; les paquets non exportés (ou seulement exportés de façon qualifiée) sont invisibles pour le code extérieur même s'ils sont public. Le module sans nom, en revanche, exporte tout ce qu'il contient.
  • Aucun module-info.java n'était nécessaire pour observer tout cela. Les trois catégories sont des faits d'exécution sur la façon dont une classe a été chargée, et getModule() combiné à getDescriptor() les expose — les mêmes appels sur lesquels les outils de migration s'appuient pour déterminer avec quoi ils travaillent.

Pourquoi trois types existent

Les deux types de compatibilité — automatique et sans nom — permettent à Java 9+ d'exécuter des applications Java 8 non modifiées. Vous optez pour une encapsulation forte un JAR à la fois : laissez tout sur le classpath (tout sans nom) et rien ne change ; déplacez une bibliothèque sur le module path sans descripteur et elle devient automatique ; ajoutez un module-info.java et elle devient nommée. Ensuite, les services de module présentent le mécanisme uses/provides qui découple les modules, et la migration des modules guide un vrai projet à travers ces trois états.

Pratique

Pratique
Lors d'une migration, vous placez un JAR tiers ordinaire (sans 'module-info.class') nommé 'fastjson-2.0.jar' sur le MODULE PATH, puis vous écrivez 'requires fastjson;' dans votre propre module nommé. Quelle affirmation décrit correctement 'fastjson' ici ?
Lors d'une migration, vous placez un JAR tiers ordinaire (sans 'module-info.class') nommé 'fastjson-2.0.jar' sur le MODULE PATH, puis vous écrivez 'requires fastjson;' dans votre propre module nommé. Quelle affirmation décrit correctement 'fastjson' ici ?
Was this page helpful?