38. Compiler, Empaqueter et Exécuter des Modules
Table des matières
- 38.1 Le Module Path vs le Classpath
- 38.2 Options de Ligne de Commande Relatives aux Modules
- 38.3 Compiler un Module Unique
- 38.4 Compiler des Modules Multiples Interdépendants
- 38.5 Empaqueter un Module dans un JAR Modulaire
- 38.6 Exécuter une Application Modulaire
- 38.7 Explication des Directives de Module
- 38.8 Modules Nommés, Automatiques et Unnamed
- 38.9 Approche Top-Down et Bottom-Up pour modulariser une application
- 38.10 Inspection des Modules et des Dépendances
- 38.11 Créer des Images Runtime Personnalisées avec jlink
- 38.12 Créer des Applications Autonomes avec jpackage
- 38.13 Résumé Final JPMS en Pratique
Une fois qu’un module est défini avec un fichier module-info.java, il doit être compilé, empaqueté et exécuté à l’aide d’outils conscients des modules.
Cette section explique comment la toolchain Java change lorsque des modules sont impliqués.
38.1 Le Module Path vs le Classpath
JPMS introduit un nouveau concept : le module path.
Il existe aux côtés du classpath traditionnel, mais les deux se comportent de manière très différente.
| Aspect | Classpath | Module path |
|---|---|---|
| Structure | Liste plate de JAR | Modules avec identité |
| Encapsulation | Aucune | Forte |
| Vérification des dépendances | Aucune | Stricte |
| Split packages | Autorisés | Interdits (modules nommés) |
| Ordre de résolution | Dépendant de l’ordre | Déterministe |
Note
- Un JAR placé sur le
module pathest traité comme unmodule:- S il contient un
module-info.class, il devient unnamed module. - S il ne contient pas de descripteur de module, il devient un
automatic module.
- S il contient un
- Un JAR placé sur le classpath n est pas traité comme un module.
- À la place, il devient partie du unnamed module, avec toutes les autres entrées du classpath.
- Un JAR modulaire (c est-à-dire un JAR contenant module-info.class) peut toujours être utilisé comme un JAR régulier.
- S il est placé sur le classpath au lieu du module path, il est traité comme partie du unnamed module, permettant aux applications non modulaires de l utiliser sans adopter le module system.
- Split packages :
- Sont autorisés sur le classpath (plusieurs JAR peuvent contenir des classes dans le même package).
- Sont interdits pour les named modules ou automatic modules sur le module path.
38.2 Options de Ligne de Commande Relatives aux Modules
Lorsqu’on travaille avec le Java Module System, java et javac fournissent des options spécifiques pour compiler et exécuter des applications modulaires.
Certaines options sont communes, tandis que d’autres sont spécifiques à un outil.
38.2.1 Options Disponibles dans java et javac
Ces options peuvent être utilisées aussi bien lors de la compilation que lors de l’exécution :
-
--moduleou-m
Utilisée pour compiler ou exécuter uniquement le module spécifié. -
--module-pathou-p
Spécifie les chemins dans lesquelsjavaoujavacrechercheront les définitions de modules.
Le système de modules Java fournit trois options spéciales en ligne de commande, utilisables avec javac et java, qui permettent de modifier temporairement les règles d’accès entre modules sans changer les fichiers module-info.java.
Ces options s’appliquent uniquement à l’exécution courante de la commande et ne modifient pas de manière permanente les descripteurs de modules.
Les trois options sont :
--add-reads--add-exports--add-opens
Elles sont généralement utilisées pour les tests, la rétrocompatibilité, les phases de migration, ou lorsque l’on travaille avec des modules tiers que l’on ne peut pas modifier.
Supposons par exemple que moduleA doive accéder aux types publics de moduleB, mais que :
moduleAne déclare pasrequires moduleB;moduleBn’exporte pas le package requis versmoduleA
Au lieu de modifier les fichiers module-info.java, il est possible d’accorder temporairement l’accès nécessaire avec :
javac --add-reads moduleA=moduleB \
--add-exports moduleB/com.modB.package1=moduleA \
...
java --add-reads moduleA=moduleB \
--add-exports moduleB/com.modB.package1=moduleA \
...
Signification des options :
-
--add-reads moduleA=moduleB
Déclare temporairement quemoduleAlitmoduleB.
Cela revient à ajouterrequires moduleB;dans le descripteur demoduleA.
Ainsi,moduleApeut accéder aux packages exportés demoduleB. -
--add-exports moduleB/com.modB.package1=moduleA
Exporte temporairement le packagecom.modB.package1du modulemoduleBversmoduleA.
Cela équivaut à ajouter :
exports com.modB.package1 to moduleA;
dans le descripteur demoduleB.
Distinction importante :
--add-readsétablit la lisibilité au niveau du module.--add-exportsaccorde l’accès à des packages spécifiques.--add-opens(non illustré ci-dessus) fonctionne comme--add-exports, mais autorise également l’accès par réflexion profonde (deep reflection), souvent nécessaire pour certains frameworks.
Ces options ne modifient pas les métadonnées compilées du module ; elles ajustent simplement le graphe des modules pour l’invocation spécifique de javac ou java.
38.2.2 Options Applicables Uniquement à javac
Ces options s’appliquent uniquement à la phase de compilation :
-
--module-source-path
(aucun raccourci)
Utilisée parjavacpour localiser les définitions des modules source. -
-d
Spécifie le répertoire de sortie dans lequel les fichiers.classseront générés après la compilation.
38.2.3 Options Applicables Uniquement à java
Ces options s’appliquent uniquement à la phase d’exécution :
-
--list-modules
(aucun raccourci)
Liste tous les modules observables puis termine. -
--show-module-resolution
(aucun raccourci)
Affiche les détails de la résolution des modules lors du démarrage de l’application. -
--describe-moduleou-d
Décrit un module spécifié puis termine.
38.2.4 Distinctions Importantes
L’option -d a des significations différentes selon l’outil :
- Dans
javac,-ddéfinit le répertoire de sortie des fichiers de classes compilés. - Dans
java,-dest un raccourci pour--describe-module.
De plus, -d ne doit pas être confondue avec -D (D majuscule).
-Dest utilisée lors de l’exécution d’un programme Java pour définir des propriétés système sous forme de paires nom-valeur sur la ligne de commande.
java -Dconfig.file=app.properties com.example.Main
Dans cet exemple, -Dconfig.file=app.properties définit une propriété système accessible à l’exécution via System.getProperty("config.file").
38.3 Compiler un Module Unique
Pour compiler un module, vous devez spécifier le chemin des sources du module et le répertoire de destination.
javac -d out \
src/com.example.hello/module-info.java \
src/com.example.hello/com/example/hello/Main.java
Une approche plus évolutive utilise --module-source-path.
javac --module-source-path src \
-d out \
$(find src -name "*.java")
Note
--module-source-path indique à javac où trouver plusieurs modules à la fois.
38.4 Compiler des Modules Multiples Interdépendants
Lorsque des modules dépendent les uns des autres, leurs dépendances doivent être résolubles à la compilation.
--module-path mods (répertoire d’exemple contenant des modules interdépendants) doit contenir des JAR modulaires déjà compilés ou des répertoires de modules compilés (chacun avec son propre module-info.class).
javac -d out \
--module-source-path src \
--module-path mods \
$(find src -name "*.java")
Ici :
--module-source-pathlocalise les arbres de sources des modules--module-pathfournit des modules déjà compilés
38.5 Empaqueter un Module dans un JAR Modulaire
Après la compilation, les modules sont généralement empaquetés sous forme de fichiers JAR.
Un JAR modulaire contient un module-info.class à sa racine.
Si module-info.class est présent, le JAR devient automatiquement un module nommé et son nom est pris depuis le descripteur (et non depuis le nom du fichier).
jar --create \
--file mods/com.example.hello.jar \
--main-class com.example.hello.Main \
-C out/com.example.hello .
Note
Un JAR avec module-info.class est un module nommé, pas un module automatique.
Lorsqu’un JAR contient un module-info.class, son nom de module est pris depuis ce fichier et n’est pas déduit du nom du fichier.
38.6 Exécuter une Application Modulaire
Pour exécuter une application modulaire, vous utilisez le module path et spécifiez le nom du module.
java --module-path mods \
--module com.example.hello/com.example.hello.Main
Vous pouvez raccourcir cela en utilisant les options -p et -m.
java -p mods -m com.example.hello/com.example.hello.Main
Note
Lors de l’utilisation de modules nommés, le classpath est ignoré pour la résolution des dépendances entre modules.
38.7 Explication des Directives de Module
Le fichier module-info.java contient des directives qui décrivent les dépendances, la visibilité et les services.
Chaque directive a une signification précise.
38.7.1 requires
La directive requires déclare une dépendance vers un autre module.
Sans elle, les types du module dépendant ne peuvent pas être utilisés.
module com.example.app {
requires com.example.lib;
}
Effets de requires :
- La dépendance doit être présente à la compilation et à l’exécution
- Les packages exportés du module requis deviennent accessibles
38.7.2 requires transitive
requires transitive expose une dépendance aux modules en aval.
Il propage la lisibilité.
module com.example.lib {
requires transitive com.example.util;
exports com.example.lib.api;
}
Signification :
- Tout module qui requiert com.example.lib lit automatiquement com.example.util
- Les appelants n’ont pas besoin de déclarer requires com.example.util explicitement
Note
Cela est similaire aux « dépendances publiques » dans d’autres systèmes de modules.
Lisible ≠ exporté : une dépendance transitive n’exporte pas automatiquement vos packages.
38.7.3 exports
exports rend un package accessible aux autres modules.
Seuls les packages exportés sont visibles à l’extérieur du module.
module com.example.lib {
exports com.example.lib.api;
}
Les packages non exportés restent fortement encapsulés.
38.7.4 exports ... to (Exports Qualifiés)
Un export qualifié restreint l’accès à des modules spécifiques.
module com.example.lib {
exports com.example.internal to com.example.friend;
}
Seuls les modules listés peuvent accéder au package exporté.
38.7.5 opens
opens permet un accès réflexif profond à un package.
Il est principalement utilisé par des frameworks utilisant la réflexion.
module com.example.app {
opens com.example.app.model;
}
Note
opens NE rend PAS un package accessible à la compilation. Il affecte uniquement la réflexion à l’exécution.
38.7.6 opens ... to (Opens Qualifiés)
Vous pouvez restreindre l’accès réflexif à des modules spécifiques.
module com.example.app {
opens com.example.app.model to com.fasterxml.jackson.databind;
}
Note
opens affecte la réflexion ; exports affecte la compilation et la visibilité des types.
38.7.7 Table des Directives Principales
| Directive | But |
|---|---|
requires |
Déclarer une dépendance |
requires transitive |
Propager une dépendance |
exports |
Exposer un package |
exports ... to |
Exposer à des modules spécifiques |
opens |
Autoriser la réflexion à l’exécution |
opens ... to |
Restreindre l’accès réflexif |
38.7.8 Exports vs Opens — Accès à la Compilation vs à lExécution
| Visibilité | Compilation ? | Réflexion à l’exécution ? |
|---|---|---|
exports |
Oui | Non |
opens |
Non | Oui |
exports ... to |
Oui (modules limités) | Non |
opens ... to |
Non | Oui (modules limités) |
Important
JPMS ajoute un module path, mais le classpath existe toujours. Ils peuvent coexister, mais les modules nommés ont la priorité.
38.8 Modules Nommés, Automatiques et Unnamed
JPMS supporte différents types de modules afin de permettre une migration progressive depuis le classpath.
JPMS doit interopérer avec du code legacy.
Pour supporter l’adoption progressive, la JVM reconnaît trois catégories différentes de modules.
38.8.1 Modules Nommés
Un module nommé possède un module-info.class et une identité stable.
- Encapsulation forte
- Dépendances explicites
- Support complet JPMS
38.8.2 Modules Automatiques
Un JAR sans module-info placé sur le module path devient un module automatique.
Son nom est dérivé du nom du fichier JAR.
- Lit tous les autres modules
- Exporte tous les packages
- Pas d’encapsulation forte
Note
Les modules automatiques existent pour faciliter la migration. Ils ne conviennent pas comme conception à long terme.
38.8.3 Module Unnamed
Le code sur le classpath appartient au module unnamed.
- Lit tous les modules nommés
- Tous les packages sont ouverts
- Ne peut pas être requis par des modules nommés
Note
Le module unnamed préserve le comportement legacy du classpath.
38.8.4 Résumé Comparatif
| Type de module | module-info présent ? | Encapsulation | Lit |
|---|---|---|---|
Named |
Oui | Forte | Déclarés seulement |
Automatic |
Non | Faible | Tous les modules |
Unnamed |
Non | Aucune | Tous les modules |
38.9 Approche Top-Down et Bottom-Up pour modulariser une application
Lors de la migration d’une application existante (non modulaire) vers le Java Platform Module System (JPMS), deux stratégies principales peuvent être adoptées : top-down et bottom-up.
Ces deux approches nécessitent une compréhension claire des interactions entre les modules nommés, les modules automatiques et le module non nommé.
38.9.1 Approche Top-Down
Dans une approche top-down, on commence par modulariser le module principal de l’application, puis on migre progressivement ses dépendances.
38.9.1.1 Règles fondamentales
- Un JAR placé sur le module path devient un module automatique.
- Son nom est déterminé soit :
- À partir de l’entrée
Automatic-Module-Namedans le manifeste, - Soit dérivé du nom du fichier JAR (les tirets sont remplacés par des points et les numéros de version sont ignorés).
Exemple :
mysql-connector-java-8.0.11.jar→mysql.connector.java
- À partir de l’entrée
-
Un
module automatique:- Exporte tous ses packages.
- Lit tous les autres modules.
-
Un JAR placé sur le classpath appartient au module non nommé.
- Le
module non nommé:- Exporte tous ses packages.
- Peut lire tous les autres modules.
-
Cependant, il n’a pas de nom ; aucun module ne peut donc déclarer
requiresà son égard. -
Les modules nommés explicitement (avec un fichier
module-info.java) - Peuvent déclarer des dépendances à l’aide de :
requires some.module; - Peuvent dépendre :
- D’autres modules nommés
- De modules automatiques
- Ne peuvent pas dépendre du module non nommé (puisqu’il n’a pas de nom).
Conséquence importante :
Un
module nommépeut lire unmodule automatique, mais il ne peut pas lire lemodule non nommé.
38.9.1.2 Implications pratiques
Supposons :
- JAR de l’application =
A Adépend directement deBBdépend deC
Si vous modularisez A en premier :
Adoit déclarerrequires B;- Donc
Bdoit être placé sur le module path (module nommé ou automatique) - Si
Bdevient un module nommé : Cdoit également être placé sur le module path (nommé ou automatique)
Ainsi, dans une migration top-down :
- On commence par la couche application.
- On modularise progressivement les dépendances vers l’extérieur.
- Les modules automatiques sont souvent utilisés temporairement pendant la transition.
38.9.1.3 Résumé des règles d’accès
| Type de module | Exporte | Peut lire |
|---|---|---|
Module nommé |
Exportations déclarées uniquement | Modules requis uniquement |
Module automatique |
Tous les packages | Tous les modules |
Module non nommé |
Tous les packages | Tous les modules |
Important
- Les
modules automatiques et non nomméssont permissifs. - Les
modules nommésimposent des règles explicites de dépendance et d’export.
38.9.2 Approche Bottom-Up
Dans une approche bottom-up, on commence par modulariser les bibliothèques de plus bas niveau, puis on progresse vers les modules de niveau supérieur, jusqu’à l’application principale.
38.9.2.1 Stratégie principale
On convertit d’abord les bibliothèques fondamentales en modules nommés correctement définis avec un descripteur explicite module-info.java.
Ensuite :
- Les modules qui en dépendent sont modularisés.
- Enfin, l’application principale devient elle aussi un module nommé.
Cette approche met l’accent sur :
- Des relations
requiresexplicites - Des
exportsmaîtrisés - Une encapsulation forte dès le départ
38.9.2.2 Avantages architecturaux
Comparés aux modules automatiques :
- Les modules nommés n’exportent que ce qui est explicitement déclaré.
- Ils ne lisent pas implicitement tous les autres modules.
- Les frontières d’encapsulation sont clairement définies.
La modularisation bottom-up conduit généralement à :
- Un graphe de dépendances plus propre
- Une meilleure maintenabilité
- Des frontières de modules plus solides
38.9.3 Comparaison conceptuelle
Top-Down
- On commence par l’application principale.
- Les dépendances sont modularisées selon les besoins.
- On s’appuie souvent temporairement sur des modules automatiques.
- Migration initiale plus rapide.
Bottom-Up
- On commence par les bibliothèques cœur.
- Les descripteurs de modules sont définis strictement dès le départ.
- La migration progresse vers le haut.
- Architecture modulaire plus disciplinée et robuste.
38.9.4 Perspective de migration
En pratique, les projets réels combinent souvent les deux stratégies :
- Une migration top-down permet d’activer rapidement l’exécution modulaire.
- Une phase de raffinement bottom-up remplace ensuite les modules automatiques par des modules nommés correctement définis.
Cette approche hybride permet une adoption progressive du JPMS tout en renforçant progressivement l’encapsulation et la clarté architecturale.
38.10 Inspection des Modules et des Dépendances
38.10.1 Décrire les Modules avec java
java --describe-module java.sql
Cela affiche exports, requires et services d’un module.
38.10.2 Décrire les JAR Modulaires
jar --describe-module --file mylib.jar
38.10.3 Analyser les Dépendances avec jdeps
jdeps analyse statiquement les dépendances de classes et de modules.
jdeps myapp.jar
jdeps --module-path mods --check my.module
Pour détecter l’utilisation d’API internes du JDK :
jdeps --jdk-internals myapp.jar
38.11 Créer des Images Runtime Personnalisées avec jlink
jlink construit un runtime Java minimal contenant uniquement les modules requis par une application.
jlink
--module-path $JAVA_HOME/jmods:mods
--add-modules com.example.app
--output runtime-image
Avantages :
- runtime plus petit
- démarrage plus rapide
- aucun module JDK inutilisé
38.12 Créer des Applications Autonomes avec jpackage
jpackage construit des installateurs spécifiques à la plateforme ou des images applicatives.
jpackage
--name MyApp
--input mods
--main-module com.example.app/com.example.Main
jpackage peut produire :
- .exe / .msi (Windows)
- .pkg / .dmg (macOS)
- .deb / .rpm (Linux)
38.13 Résumé Final JPMS en Pratique
JPMSintroduit uneencapsulation forteet des dépendances fiables- Les
modulesremplacent les conventions fragiles du classpath - Les
servicespermettent des architectures découplées - Les
modules automatiqueset lemodule unnamedsupportent la migration jlinketjpackagepermettent des modèles modernes de déploiement