Comment utiliser les commandes pour découvrir les modules Java?

Jusqu’à présent, nous avons travaillé avec des modules que nous avons écrits. Même les classes intégrées au JDK sont modularisées. Dans cette section, nous vous montrons comment utiliser des commandes pour en apprendre davantage sur les modules.

Vous n’avez pas besoin de connaître la sortie des commandes dans cette section. Vous devez, cependant, connaître la syntaxe des commandes et ce qu’elles font. Nous incluons la sortie là où cela facilite la mémorisation de ce qui se passe.

Identifier les Modules Intégrés

Le module le plus important à connaître est java.base. Il contient la plupart des packages que vous avez appris jusqu’à présent. En fait, il est tellement important que vous n’avez même pas à utiliser la directive requires ; il est disponible pour toutes les applications modulaires. Votre fichier module-info.java se compilera toujours si vous exigez explicitement java.base. Cependant, c’est redondant, donc il est préférable de l’omettre. Le tableau 12.6 liste certains modules courants et ce qu’ils contiennent.

Compiler du code non modulairejavac -cp classpath -d directory classesToCompile
javac –class-path classpath -d directory classesToCompile
javac -classpath classpath -d directory classesToCompile
Exécuter du code non modulairejava -cp classpath package.className
java -classpath classpath package.className
java –class-path classpath package.className
Compiler un modulejavac -p moduleFolderName -d directory classesToCompileIncludingModuleInfo
javac –module-path moduleFolderName -d directory classesToCompileIncludingModuleInfo
Exécuter un modulejava -p moduleFolderName -m moduleName/package.className
java –module-path moduleFolderName –module moduleName/package.className
Décrire un modulejava -p moduleFolderName -d moduleName
java –module-path moduleFolderName –describe-module moduleName
jar –file jarName –describe-module
jar -f jarName -d
Lister les modules disponiblesjava –module-path moduleFolderName –list-modules
java -p moduleFolderName –list-modules
Afficher les dépendancesjdeps -summary –module-path moduleFolderName jarName
jdeps -s –module-path moduleFolderName jarName
jdeps –jdk-internals jarName
jdeps -jdkinternals jarName
Afficher la résolution des modulesjava –show-module-resolution -p moduleFolderName -m moduleName
java –show-module-resolution –module-path moduleFolderName –module moduleName
Créer un JAR d’exécutionjlink -p moduleFolderName –add-modules moduleName –output zooApp
jlink –module-path moduleFolderName –add-modules moduleName –output zooApp

Le tableau 12.11 montre les options pour javac, le tableau 12.12 montre les options pour java, le tableau 12.13 montre les options pour jar, et le tableau 12.14 montre les options pour jdeps. Enfin, le tableau 12.15 montre les options pour jlink.

OptionDescription
-cp <classpath>
-classpath <classpath>
–class-path <classpath>
Emplacement des JAR dans un programme non modulaire
-d <dir>Répertoire dans lequel placer les fichiers de classe générés
-p <path>
–module-path <path>
Emplacement des JAR dans un programme modulaire
OptionDescription
-p <path>
–module-path <path>
Emplacement des JAR dans un programme modulaire
-m <name>
–module <name>
Nom du module à exécuter
-d
–describe-module
Décrit les détails du module
–list-modulesListe les modules observables sans exécuter le programme
–show-module-resolutionAffiche les modules lors de l’exécution du programme
OptionDescription
-c
–create
Crée un nouveau fichier JAR
-v
–verbose
Imprime les détails lors du travail avec les fichiers JAR
-f
–file
Nom du fichier JAR
-CRépertoire contenant les fichiers à utiliser pour créer JAR
-d
–describe-module
Décrit les détails du module
OptionDescription
–module-path <path>Emplacement des JAR dans un programme modulaire
-s
-summary
Résume la sortie
–jdk-internals
-jdkinternals
Liste les utilisations des API internes
OptionDescription
-p
–module-path <path>
Emplacement des JAR dans un programme modulaire
–add-modulesListe des modules à empaqueter
–outputNom du répertoire de sortie

h>Nom du moduleCe qu’il contientCouverture dans le livrejava.baseCollections, math, IO, NIO.2, concurrency, etc.La plupart de ce livrejava.desktopAbstract Windows Toolkit (AWT) et SwingPas au-delà du nom du modulejava.loggingJournalisationPas au-delà du nom du modulejava.sqlJDBCChapitre 15, “JDBC”java.xmlExtensible Markup Language (XML)Pas au-delà du nom du module

Il est important de reconnaître les noms des modules fournis par le JDK. Bien que vous n’ayez pas besoin de connaître les noms par cœur, vous devez être capable de les identifier.

Vous devez savoir que les noms de modules commencent par java pour les API que vous êtes susceptible d’utiliser et par jdk pour les API spécifiques au JDK. Le tableau 12.7 liste tous les modules qui commencent par java.

java.basejava.namingjava.smartcardio
java.compilerjava.net.httpjava.sql
java.datatransferjava.prefsjava.sql.rowset
java.desktopjava.rmijava.transaction.xa
java.instrumentjava.scriptingjava.xml
java.loggingjava.sejava.xml.crypto
java.managementjava.security.jgss 
java.management.rmijava.security.sasl 

Le tableau 12.8 liste tous les modules qui commencent par jdk. Nous recommandons de revoir cette liste pour augmenter les chances qu’ils vous semblent familiers. Rappelez-vous que vous n’avez pas à les mémoriser.

jdk.accessibilityjdk.javadocjdk.management.agent
jdk.attachjdk.jcmdjdk.management.jfr
jdk.charsetsjdk.jconsolejdk.naming.dns
jdk.compilerjdk.jdepsjdk.naming.rmi
jdk.crypto.cryptokijdk.jdijdk.net
jdk.crypto.ecjdk.jdwp.agentjdk.nio.mapmode
jdk.dynalinkjdk.jfrjdk.sctp
jdk.editpadjdk.jlinkjdk.security.auth
jdk.hotspot.agentjdk.jshelljdk.security.jgss
jdk.httpserverjdk.jsobjectjdk.xml.dom
jdk.incubator.foreignjdk.jstatdjdk.zipfs
jdk.incubator.vectorjdk.localedata 
jdk.jartooljdk.management 

Obtenir des Détails avec java

La commande java a trois options liées aux modules. L’une décrit un module, une autre liste les modules disponibles, et la troisième montre la logique de résolution des modules.

Il est également possible d’ajouter des modules, des exports, et plus encore en ligne de commande. Mais s’il vous plaît, ne le faites pas. C’est déroutant et difficile à maintenir. Notez que ces options sont disponibles sur java mais pas sur toutes les commandes.

Décrire un Module

Supposons que vous ayez le fichier JAR du module zoo.animal.nourriture et que vous souhaitiez connaître sa structure. Vous pourriez le « décompresser » et ouvrir le fichier module-info.java. Cela vous montrerait que le module exporte un package et ne requiert explicitement aucun module.

module zoo.animal.nourriture {
    exports zoo.animal.nourriture;
}

Cependant, il existe une manière plus simple. La commande java dispose d’une option pour décrire un module. Les deux commandes suivantes sont équivalentes :

java -p mods
    -d zoo.animal.nourriture

java -p mods
    --describe-module zoo.animal.nourriture

Chacune affiche des informations sur le module. Par exemple, elle pourrait imprimer ceci :

zoo.animal.nourriture file:///absolutePath/mods/zoo.animal.nourriture.jar
exports zoo.animal.nourriture
requires java.base mandated

La première ligne est le module que nous avons demandé : zoo.animal.nourriture. La deuxième ligne commence par des informations sur le module. Dans notre cas, c’est la même déclaration d’exportation de package que nous avions dans le fichier de déclaration de module.

Sur la troisième ligne, nous voyons requires java.base mandated. Attendez une minute. La déclaration de module ne spécifie clairement aucun module dont zoo.animal.nourriture dépend.

Rappelez-vous, le module java.base est spécial. Il est automatiquement ajouté comme dépendance à tous les modules. Ce module contient des packages fréquemment utilisés comme java.util. C’est ce que signifie mandated. Vous obtenez java.base que vous l’ayez demandé ou non.

Dans les classes, le package java.lang est automatiquement importé, que vous le tapiez ou non. Le module java.base fonctionne de la même manière. Il est automatiquement disponible pour tous les autres modules.

Plus d’informations sur la description des modules

Vous devez seulement savoir comment exécuter --describe-module plutôt que d’interpréter la sortie. Cependant, vous pourriez rencontrer quelques surprises lors de l’expérimentation avec cette fonctionnalité, nous les décrivons donc un peu plus en détail ici.

Supposons que les éléments suivants sont le contenu de module-info.java dans zoo.animal.soins :

module zoo.animal.soins {
    exports zoo.animal.soins.medical to zoo.staff;
    requires transitive zoo.animal.nourriture;
}

Maintenant, nous avons la commande pour décrire le module et la sortie.

java -p mods -d zoo.animal.soins

zoo.animal.soins file:///absolutePath/mods/zoo.animal.soins.jar
requires zoo.animal.nourriture transitive
requires java.base mandated
qualified exports zoo.animal.soins.medical to zoo.staff
contains zoo.animal.soins.details

La première ligne de la sortie est le chemin absolu du fichier de module. Les deux lignes requires devraient sembler familières également. La première est dans le module-info, et l’autre est ajoutée à tous les modules. Ensuite vient quelque chose de nouveau. Le qualified exports est le nom complet du package que nous exportons vers un module spécifique.

Enfin, le contains signifie qu’il y a un package dans le module qui n’est pas exporté du tout. C’est vrai. Notre module a deux packages, et l’un n’est disponible que pour le code à l’intérieur du module.

Lister les Modules Disponibles

En plus de décrire les modules, vous pouvez utiliser la commande java pour lister les modules qui sont disponibles. La forme la plus simple liste les modules qui font partie du JDK.

java --list-modules

Lorsque nous l’avons exécutée, la sortie s’est étendue sur 70 lignes et ressemblait à ceci :

java.base@17
java.compiler@17
java.datatransfer@17

Il s’agit d’une liste de tous les modules qui viennent avec Java et leurs numéros de version. Vous pouvez dire que nous utilisions Java 17 lors du test de cet exemple.

Plus intéressant, vous pouvez utiliser cette commande avec du code personnalisé. Essayons à nouveau avec un répertoire contenant nos modules zoo.

java -p mods --list-modules

Combien de lignes attendez-vous dans la sortie cette fois ? Il y a 78 lignes maintenant : les 70 modules intégrés plus les 8 que nous avons créés dans ce chapitre. Deux des lignes personnalisées ressemblent à ceci :

zoo.animal.soins file:///absolutePath/mods/zoo.animal.soins.jar
zoo.animal.nourriture file:///absolutePath/mods/zoo.animal.nourriture.jar

Puisqu’il s’agit de modules personnalisés, nous obtenons un emplacement sur le système de fichiers. Si le projet avait un numéro de version de module, il aurait à la fois le numéro de version et le chemin du système de fichiers.

Notez que --list-modules se termine dès qu’il imprime les modules observables. Il n’exécute pas le programme.

Afficher la Résolution des Modules

Si la liste des modules ne vous donne pas assez de sortie, vous pouvez également utiliser l’option --show-module-resolution. Vous pouvez la considérer comme un moyen de déboguer les modules. Elle affiche beaucoup de sortie lorsque le programme démarre. Puis elle exécute le programme.

java --show-module-resolution
    -p nourriture
    -m zoo.animal.nourriture/zoo.animal.nourriture.Tache

Heureusement, vous n’avez pas besoin de comprendre cette sortie. Cela dit, l’avoir vu facilitera la mémorisation. Voici un extrait de la sortie :

root zoo.animal.nourriture file:///absolutePath/nourriture/
java.base binds java.desktop jrt:/java.desktop
java.base binds jdk.jartool jrt:/jdk.jartool
…
jdk.security.auth requires java.naming jrt:/java.naming
jdk.security.auth requires java.security.jgss jrt:/java.security.jgss
…
Tout nourri !

Elle commence par lister le module racine. C’est celui que nous exécutons : zoo.animal.nourriture. Ensuite, elle liste de nombreuses lignes de packages inclus par le module java.base obligatoire. Après un moment, elle liste les modules qui ont des dépendances. Enfin, elle affiche le résultat du programme : Tout nourri !

Décrire avec jar

Comme la commande java, la commande jar peut décrire un module. Ces commandes sont équivalentes :

jar -f mods/zoo.animal.nourriture.jar -d
jar --file mods/zoo.animal.nourriture.jar --describe-module

La sortie est légèrement différente de celle que nous avons obtenue lorsque nous avons utilisé la commande java pour décrire le module. Avec jar, elle affiche ce qui suit :

zoo.animal.nourriture jar:file:///absolutePath/mods/zoo.animal.nourriture.jar
/!module-info.class
exports zoo.animal.nourriture
requires java.base mandated

La version JAR inclut le module-info.class dans le nom de fichier, ce qui n’est pas une différence particulièrement significative. Vous devez savoir que les deux commandes peuvent décrire un module.

Apprendre les Dépendances avec jdeps

La commande jdeps vous donne des informations sur les dépendances au sein d’un module. Contrairement à la description d’un module, elle examine le code en plus de la déclaration du module. Cela vous indique quelles dépendances sont réellement utilisées plutôt que simplement déclarées.

Vous êtes censé comprendre comment utiliser jdeps avec des projets qui n’ont pas encore été modularisés pour aider à identifier les dépendances et les problèmes. Tout d’abord, nous allons créer un fichier JAR à partir de cette classe. Si vous suivez, n’hésitez pas à copier la classe à partir des exemples en ligne référencés au début du chapitre plutôt que de la taper.

// Animatronique.java
package zoo.dinos;

import java.time.*;
import java.util.*;
import sun.misc.Unsafe;

public class Animatronique {
    private List noms;
    private LocalDate dateVisite;
    
    public Animatronique(List noms, LocalDate dateVisite) {
        this.noms = noms;
        this.dateVisite = dateVisite;
    }
    
    public void methodeNonSecurisee() {
        Unsafe unsafe = Unsafe.getUnsafe();
    }
}

Cet exemple est idiot. Il utilise un certain nombre de classes sans rapport. Le Zoo du Bronx avait vraiment des dinosaures électroniques pendant un moment, donc au moins l’idée d’avoir des dinosaures dans un zoo n’est pas au-delà du domaine du possible.

Maintenant, nous pouvons compiler ce fichier. Vous avez peut-être remarqué qu’il n’y a pas de fichier module-info.java. C’est parce que nous ne créons pas un module. Nous examinons les dépendances dont nous aurons besoin lorsque nous modulariserons ce JAR.

javac zoo/dinos/*.java

La compilation fonctionne, mais elle vous donne quelques avertissements concernant Unsafe étant une API interne. Ne vous inquiétez pas pour l’instant — nous en discutons bientôt. (Peut-être que les dinosaures ont disparu parce qu’ils ont fait quelque chose de non sécurisé.)

Ensuite, nous créons un fichier JAR.

jar -cvf zoo.dino.jar .

Nous pouvons exécuter la commande jdeps contre ce JAR pour en apprendre davantage sur ses dépendances. Tout d’abord, exécutons la commande sans options. Sur les deux premières lignes, la commande imprime les modules que nous devrions ajouter avec une directive requires pour migrer vers le système de modules. Elle imprime également un tableau montrant quels packages sont utilisés et à quels modules ils correspondent.

jdeps zoo.dino.jar

zoo.dino.jar -> java.base
zoo.dino.jar -> jdk.unsupported
zoo.dinos -> java.lang java.base
zoo.dinos -> java.time java.base
zoo.dinos -> java.util java.base
zoo.dinos -> sun.misc JDK internal API (jdk.unsupported)

Notez que java.base est toujours inclus. Il indique également quels modules contiennent les classes utilisées par le JAR. Si nous exécutons en mode résumé, nous ne voyons que la première partie où jdeps liste les modules. Il existe deux formats pour le drapeau de résumé :

jdeps -s zoo.dino.jar
jdeps -summary zoo.dino.jar

zoo.dino.jar -> java.base
zoo.dino.jar -> jdk.unsupported

Pour un projet réel, la liste des dépendances pourrait inclure des dizaines, voire des centaines de packages. Il est utile de voir le résumé des modules uniquement. Cette approche facilite également la visualisation de la présence de jdk.unsupported dans la liste.

Il existe également une option --module-path que vous pouvez utiliser si vous souhaitez rechercher des modules en dehors du JDK. Contrairement à d’autres commandes, il n’y a pas de forme abrégée pour cette option sur jdeps.

Vous avez peut-être remarqué que jdk.unsupported ne figure pas dans la liste des modules que vous avez vue dans le tableau 12.8. Il est spécial car il contient des bibliothèques internes que les développeurs des versions précédentes de Java étaient découragés d’utiliser, bien que beaucoup aient ignoré cet avertissement. Vous ne devriez pas y faire référence, car il pourrait disparaître dans les futures versions de Java.

Utilisation du drapeau –jdk-internals

La commande jdeps a une option pour fournir des détails sur ces API non supportées. La sortie ressemble à ceci :

jdeps --jdk-internals zoo.dino.jar

zoo.dino.jar -> jdk.unsupported
    zoo.dinos.Animatronique -> sun.misc.Unsafe
                           JDK internal API (jdk.unsupported)

Warning: 

JDK Internal API           Suggested Replacement
----------------          ---------------------
sun.misc.Unsafe           See http://openjdk.java.net/jeps/260

L’option --jdk-internals liste toutes les classes que vous utilisez qui appellent une API interne ainsi que l’API concernée. À la fin, elle fournit un tableau suggérant ce que vous devriez faire à ce sujet. Si vous avez écrit le code appelant l’API interne, ce message est utile. Sinon, le message serait utile pour l’équipe qui a écrit le code. Vous, en revanche, pourriez avoir besoin de mettre à jour ou de remplacer entièrement ce fichier JAR par un qui résout le problème. Notez que -jdkinternals est équivalent à --jdk-internals.

Scénario du Monde Réel

À propos de sun.misc.Unsafe

Avant le Système de Module de la Plateforme Java, les classes devaient être public si vous vouliez qu’elles soient utilisées en dehors du package. Il était raisonnable d’utiliser la classe dans le code JDK puisqu’il s’agit de code de bas niveau déjà étroitement couplé au JDK. Comme elle était nécessaire dans plusieurs packages, la classe a été rendue public. Sun l’a même nommée Unsafe, pensant que cela empêcherait quiconque de l’utiliser en dehors du JDK.

Cependant, les développeurs sont astucieux et ont utilisé la classe puisqu’elle était disponible. Un certain nombre de bibliothèques open source largement utilisées ont commencé à utiliser Unsafe. Bien qu’il soit très peu probable que vous utilisiez cette classe dans votre projet directement, vous utilisez probablement une bibliothèque open source qui l’utilise.

La commande jdeps vous permet d’examiner ces JAR pour voir si vous aurez des problèmes lorsqu’Oracle empêchera finalement l’utilisation de cette classe. Si vous trouvez des utilisations, vous pouvez vérifier s’il existe une version plus récente du JAR que vous pouvez mettre à niveau.

Utilisation des Fichiers Module avec jmod

La dernière commande que vous devez connaître est jmod. Vous pourriez penser qu’un JMOD est un fichier de module Java. Pas tout à fait. Oracle recommande d’utiliser des fichiers JAR pour la plupart des modules. Les fichiers JMOD ne sont recommandés que lorsque vous avez des bibliothèques natives ou quelque chose qui ne peut pas aller dans un fichier JAR. Cela est peu susceptible de vous affecter dans le monde réel.

La chose la plus importante à retenir est que jmod ne sert qu’à travailler avec les fichiers JMOD. Le tableau 12.9 liste les modes courants.

OpérationDescription
createCrée un fichier JMOD.
extractExtrait tous les fichiers du JMOD. Fonctionne comme la décompression.
describeImprime les détails du module tels que requires.
listListe tous les fichiers dans le fichier JMOD.
hashImprime ou enregistre les hachages.

Création d’environnements d’exécution Java avec jlink

L’un des avantages des modules est de pouvoir fournir uniquement les parties de Java dont vous avez besoin. Notre exemple de zoo du début du chapitre n’a pas beaucoup de dépendances. Si l’utilisateur n’a pas déjà Java ou est sur un appareil sans beaucoup de mémoire, télécharger un JDK de plus de 150 Mo est une grosse demande. Voyons à quel point le package doit réellement être gros ! Cette commande crée notre distribution plus petite :

jlink --module-path mods --add-modules zoo.animal.talks --output zooApp

D’abord, nous spécifions où trouver les modules personnalisés avec -p ou --module-path. Ensuite, nous spécifions nos noms de modules avec --add-modules. Cela inclura les dépendances tant qu’elles peuvent être trouvées. Enfin, nous spécifions le nom du dossier de notre JDK plus petit avec --output.

Le répertoire de sortie contient les répertoires bin, conf, include, legal, lib et man ainsi qu’un fichier de version. Ceux-ci devraient vous sembler familiers car vous les trouvez également dans le JDK complet.

Lorsque nous exécutons cette commande et compressons le répertoire zooApp, le fichier ne fait que 15 Mo. C’est d’un ordre de grandeur plus petit que le JDK complet. D’où vient cette économie d’espace ? Il y a de nombreux modules dans le JDK dont nous n’avons pas besoin. De plus, les outils de développement comme javac n’ont pas besoin d’être dans une distribution d’exécution.

Il y a beaucoup plus d’éléments pour personnaliser ce processus. Par exemple, vous pouvez sauter la génération de la documentation d’aide et économiser encore plus d’espace.