W3docs

Introduction aux modules Java (JPMS)

Ce que sont les modules Java, les problèmes que JPMS résout et sa relation avec le classpath.

Le Java Platform Module System (JPMS), introduit dans Java 9, ajoute une couche au-dessus des packages. Un module est un groupe de packages nommé et auto-descriptif qui déclare explicitement deux choses : ce dont il a besoin des autres modules, et ce qu'il leur offre. Cette déclaration réside dans un seul fichier, module-info.java, à la racine du module. Cette partie du livre l'explore en détail ; ce chapitre explique pourquoi il existe.

Le problème que JPMS résout : le classpath

Avant Java 9, chaque JAR était placé sur un classpath plat et unique. Cette organisation présentait des problèmes chroniques :

  • Aucune encapsulation. Chaque classe public de chaque JAR était accessible par tous. Une classe destinée uniquement à un usage interne (sun.misc.Unsafe, com.example.internal.*) pouvait être utilisée par n'importe qui et ne pouvait donc jamais être modifiée en toute sécurité.
  • Aucune dépendance déclarée. Un JAR ne précisait jamais quels autres JARs il nécessitait. On découvrait une dépendance manquante seulement lorsqu'une NoClassDefFoundError se produisait à l'exécution — potentiellement en production.
  • L'enfer des JARs. Deux JARs fournissant le même package, ou deux versions de la même bibliothèque, étaient silencieusement fusionnés selon l'ordre du classpath. La classe chargée en premier gagnait.

Les modules attaquent ces trois problèmes : un module cache tout package qu'il n'export pas explicitement, déclare chaque module qu'il requires, et la JVM vérifie l'ensemble du graphe au démarrage — les modules manquants ou dupliqués échouent rapidement.

Modules, packages et JARs

Ces trois concepts sont faciles à confondre :

ConceptCe qu'il regroupeRègle de visibilité
Packageclassespublic/protected/package-private au sein du JAR
JARpackages + ressourcestout ce qui est public est visible sur le classpath
Modulepackagesseuls les packages exported sont visibles pour les autres modules

Un module est généralement empaqueté sous forme de JAR (un « JAR modulaire » — un JAR ordinaire avec un module-info.class à sa racine). La différence tient au descripteur : placez le JAR sur le classpath et les règles sont ignorées ; placez-le sur le module path et JPMS les applique.

L'encapsulation forte, en une phrase

La règle principale : un package est invisible pour les autres modules sauf si son module l'exports — même si ses classes sont public. public signifie désormais « accessible au code qui peut lire ce package », et lire un package nécessite un exports ainsi qu'un requires. C'est pourquoi le JDK lui-même a pu finalement cacher ses éléments internes : java.base exporte java.util mais pas jdk.internal.misc.

Le JDK est lui aussi modulaire

Depuis Java 9, le JDK est divisé en ~70 modules (java.base, java.sql, java.xml, java.net.http, …). java.base est spécial : il est implicitement requis par chaque module et contient les éléments essentiels du langage (java.lang, java.util, java.io). Chaque classe que vous avez jamais utilisée vit dans l'un de ces modules — ce que l'exemple pratique ci-dessous rend visible.

Exemple pratique : inspecter les modules à l'exécution

Il n'est pas nécessaire d'écrire un module pour voir les modules : l'API Module du runtime indique le module de n'importe quelle classe. Ce programme interroge plusieurs classes sur leur module d'appartenance, vérifie son propre module, et consulte la couche de démarrage que la JVM a initialisée.

java— editable, runs on the server

Ce que retenir de l'exécution :

  • String, ArrayList et HttpClient ont rapporté respectivement java.base, java.base et java.net.http. Chaque classe appartient exactement à un module, et getModule() vous l'indique — les types du langage vivent tous dans java.base, tandis que HttpClient se trouve dans son propre module, que vous devriez déclarer requires java.net.http pour utiliser.
  • La propre classe du programme a rapporté isNamed() == false et un getName() valant null. Le code exécuté depuis le classpath atterrit dans le module sans nom, un compartiment de compatibilité qui ne requiert rien explicitement et lit chaque autre module. C'est pourquoi les programmes utilisant le classpath continuent à compiler et s'exécuter sans changement sur Java 9+.
  • ModuleLayer.boot() a exposé le graphe des modules que la JVM a résolus au démarrage — compter les modules java.* montre que le JDK est vraiment divisé en de nombreux modules, pas en un seul monolithe.
  • java.base n'est pas ouvert (isOpen() == false) mais exporte de nombreux packages ; il expose java.lang et java.util à tous en gardant jdk.internal.* caché. Exporter un package et ouvrir un module sont deux interrupteurs distincts — un chapitre ultérieur reviendra sur opens.
  • Rien ici ne nécessite un module-info.java. L'API Module est une métadonnée réflexive disponible pour tout programme ; écrire votre propre module (chapitre suivant) est ce qui vous permet de déclarer ces règles plutôt que de simplement observer celles du JDK.

Ce que couvre le reste de cette partie

  • module-info.java — les directives : requires, exports, opens, uses, provides.
  • Types de modules — modules nommés, automatiques et sans nom, et comment ils se mélangent.
  • Services — découpler une interface de son implémentation avec uses/provides et ServiceLoader.
  • Migration — déplacer une application classpath existante vers le module path sans réécriture complète.

Les modules sont facultatifs : une application Java peut fonctionner indéfiniment sur le classpath. Mais les comprendre explique le JDK moderne, débloque les runtimes personnalisés jlink, et donne aux bibliothèques une encapsulation réelle. Le chapitre suivant, la déclaration module-info.java, écrit le descripteur qui fait d'un module un module.

Pratique

Pratique
Une bibliothèque JAR contient une classe 'public' dans le package 'com.acme.internal' que les auteurs destinent uniquement à un usage interne. Le JAR est construit comme un JAR modulaire dont le 'module-info.java' exporte 'com.acme.api' mais pas 'com.acme.internal'. Que se passe-t-il quand ce JAR est placé sur le MODULE PATH et qu'un autre module tente d'importer 'com.acme.internal.Helper' ?
Une bibliothèque JAR contient une classe 'public' dans le package 'com.acme.internal' que les auteurs destinent uniquement à un usage interne. Le JAR est construit comme un JAR modulaire dont le 'module-info.java' exporte 'com.acme.api' mais pas 'com.acme.internal'. Que se passe-t-il quand ce JAR est placé sur le MODULE PATH et qu'un autre module tente d'importer 'com.acme.internal.Helper' ?
Was this page helpful?