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.jar→guava), sauf si le manifeste du JAR définitAutomatic-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()renvoienull,isNamed()vautfalse). - 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é | Automatique | Sans nom |
|---|---|---|---|
| Nommé | uniquement si requires | uniquement si requires | jamais |
| Automatique | oui | oui | oui |
| Sans nom | oui | oui | oui |
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.
Ce que l'on retient de l'exécution :
- La propre classe du programme est classifiée comme UNNAMED avec un nom
null, tandis quejava.util.ListetHttpClientsont 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éfalsemaisself.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éclarerrequiressur du code classpath.classify()a distingué automatique de nommé viadescriptor.isAutomatic(). Vous ne verrez pastrueici (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étruemaisisExported("jdk.internal.misc")a renvoyéfalse, même si les deux sont de vrais paquets à l'intérieur dejava.base. Leexportsd'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 sontpublic. Le module sans nom, en revanche, exporte tout ce qu'il contient.- Aucun
module-info.javan'é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, etgetModule()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.