IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

 

Développons en Java   2.30  
Copyright (C) 1999-2022 Jean-Michel DOUDOUX    (date de publication : 15/06/2022)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

33. Le système de modules de la plateforme Java

 

chapitre    3 3

 

Niveau : niveau 1 Fondamental 

 

La fonctionnalité majeure de Java 9 est le système de modules de la plate-forme Java (Java Platform Module System ou JPMS). JPMS modularise les bibliothèques de classes fournies par le JDK. JPMS peut aussi être utilisé par les développeurs pour modulariser les applications ou les bibliothèques. Cela permet aux développeurs de décomposer leurs applications en modules. Ces modules peuvent ensuite spécifier les autres modules dont ils ont besoin et les packages qu'ils exposent pour être utilisés par d'autres modules.

Le système de modules de la plateforme Java (JPMS) est issu des travaux du projet Jigsaw.

Les principaux objectifs de l'introduction de la modularisation dans la plate-forme Java sont :

  • un renforcement de l'encapsulation qui améliore la maintenance des applications, des bibliothèques et des frameworks
  • une meilleure fiabilité de la configuration en permettant la déclaration explicite des dépendances entre modules
  • une amélioration de la sécurité
  • une amélioration des performances notamment dans le chargement des types
  • la possibilité de réduire la taille du JDK pour faciliter son déploiement sur des machines à ressources réduites ou dans le cloud

L'écosystème Java dispose déjà de plusieurs systèmes de modules notamment OSGi et JBoss Modules. L'approche de ces systèmes existants est différente : ils ont été créés et utilisés pour des cas d'utilisation concrets et réels. Le but de JPMS est d'être globalement une extension de la JVM pour permettre un support des modules.

JPMS impacte de nombreux éléments de la plateforme Java : les bibliothèques, le langage, la JVM et les outils.

Le système de modules de la plateforme Java intègre :

  • la modularisation du JDK : le but est de diviser le JDK en différents petits modules car le fichier rt.jar historique est monolithique et trop gros
  • l'encapsulation dans des modules dédiés de la plupart des API internes du JDK. Quelques-unes de ces API qui sont largement utilisés seront cependant encore accessible pour limiter les impacts dans un premier temps
  • l'outil jlink qui permet de créer un JRE personnalisé pour une application : ce JRE ne contient alors que les modules utiles et nécessaires à l'exécution de cette application

JPMS ajoute le concept de modules à Java qui est un nouvel élément structurant :

  • une classe contient des champs et des méthodes
  • un package contient des types (classes,interfaces, ...) et éventuellement des ressources
  • un module contient des packages

L'utilisation du système de modules impliquent plusieurs choses :

  • assurer une configuration fiable des dépendances des modules
  • une encapsulation forte
  • une identification des modules notamment pour la déclaration et la recherche des dépendances
  • une potencielle réorganisation du code notamment pour du code legacy

L'utilisation de modules permet :

  • de mettre en oeuvre une forte encapsulation (strong encapsulation). Les modules peuvent masquer l'accès à des classes internes dont l'implémentation ne doit pas être exposée. Par défaut, un package d'un module n'est pas visible à l'extérieur du module quelle que soit la visibilité des types qu'il contient. Ceci apporte de nombreux bénéfice en termes de design. Il ne sera plus nécessaire de définir des packages préfixés par impl ou internal pour lesquels la Javadoc précise qu'il ne faut pas l'utiliser les classes qu'ils contiennent
  • de fournir une liste des modules utilisés comme dépendances
  • de limiter la taille en ne prenant que les modules utiles pour construire un JRE personnalisé
  • d'améliorer les performances
  • de renforcer la sécurité
  • de faciliter les futures évolutions

Un module peut être packagé sous la forme d'un jar modulaire qui est un fichier jar qui contient un fichier module-info.class à sa racine.

Le fichier module-info.class est issu de la compilation d'un fichier particulier nommé module-info.java. Ce fichier est le descripteur du module : il définit entre autres le nom du module, les packages dont les classes publiques peuvent être accédées par d'autres modules, les module requis (les dépendances), les services fournis ou consommés, ...

Pour utiliser un jar modulaire comme module, celui-ci doit être ajouté dans le modulepath et non plus dans le classpath. L'avantage que cela soit toujours un jar est qu'est toujours possible d'utiliser un jar dans le classpath. Dans ce cas, le fichier sera utilisé comme un fichier jar classique et non pas comme un module même s'il contient un fichier module-info.class qui sera simplement ignoré dans ce cas. Dans cette situation, tous les packages sont accessibles puisque le fichier est chargé via le classpath.

La modularité est une fonctionnalité essentielle de la plateforme Java pour lui permettre de futures évolutions.

Ce chapitre contient plusieurs sections :

 

33.1. La modularisation

La modularité est un principe de conception qui permet :

  • un couplage faible entre les composants
  • de définir un contrat et les dépendances entre des composants
  • de masquer le détail des implémentations en utilisant une encapsulation forte

Dans le domaine des logiciels, la modularité consiste à l'écriture et à la mise en oeuvre d'un programme ou d'un système informatique sous la forme d'un certain nombre de modules, plutôt que sous la forme d'une conception monolithique. Cela revient à implémenter une application ou un système sous la forme d'un ensemble de modules.

La mise en oeuvre de modules encourage à utiliser des bonnes pratiques de conception telles que l'encapsulation et la séparation des préoccupations (separation of concerns).

Des interfaces sont utilisées pour permettre aux modules de communiquer. L'utilisation de modules permet de réduire le couplage et de faciliter le développement d'applications en réduisant la complexité du système.

D'une manière générale dans un langage de programmation, un module est un artéfact qui contient du code et des méta données (description du module, relation avec les autres modules, ...)

Un module devrait avoir certaines caractéristiques :

  • une unité autonome déployable de manière simple
  • proposant une interface définissant un contrat pour la communication (couplage faible)
  • possédant une identité cohérente et unique (identifiant et version du module)
  • pouvant déclarer et utiliser d'autres modules qui sont des dépendances
  • définies via des méta-informations

Un module devrait mettre en oeuvre trois principes :

  • encapsulation forte : l'encapsulation implique de cacher les détails d'implémentation, ce qui permet de modifier l'implémentation sans impacter l'utilisation du module
  • abstraction stable : un module devrait exposer ces fonctionnalités au travers d'interfaces publiques pour réduire le couplage
  • déclaration des dépendances : la définition du module devrait contenir la liste des modules dont il dépend

 

33.2. Les difficultés pouvant être résolues par les modules

Les plateformes Java antérieures à la version 9 possèdent plusieurs inconvénients lors du développement et du déploiement d'applications :

  • la taille du JDK limite son utilisation sur des appareils limités en ressources. Java avait tenté d'apporter une solution sous la forme de trois Compact Profiles
  • le fichier rt.jar est monolithique et trop gros (53 Mo). En incluant les autre fichiers jar, on arrive à 67 Mo
  • les évolutions dans cet ensemble monolithiques ne sont pas faciles
  • il n'y a pas d'encapsulation forte, ce qui permet à toutes classes publiques d'être utilisées. Ceci inclus les classes normalement à usage interne de la JVM comme celles des packages sun.*, *.internal.*, ...
  • l'accès à toutes les classes publiques peut aussi avoir des conséquences sur la sécurité
  • le classpath ne permet pas de déterminer si toutes les classes requises sont présentes pour une exécution de l'application
  • deux classpaths doivent être gérés : un pour la compilation et un pour l'exécution

Le système de modules de Java tente d'apporter certaines solutions à ces problématiques.

 

33.2.1. La taille croissante des API du JRE

Au fur et à mesure des versions de Java, la taille de son runtime n'a fait que croître. Par exemple, l'API Core de Java 8 contient 217 packages.

Cette taille sans cesse croissante de la plate-forme Java SE rend son utilisation de plus en plus difficile sur les petits appareils malgré le fait que de nombreux appareils de ce type soient capables d'exécuter une machine virtuelle Java. Cela augmente aussi la taille des livrables, ce qui peut poser des problèmes lors de déploiements sur de nombreux noeuds d'un cluster dans le cloud.

Avant Java 8, il n'y aucun moyen de définir et utiliser un sous-ensemble du runtime. Ainsi chaque runtime inclut toutes les bibliothèques telles que AWT, Swing, XML ou même Corba même si l'application n'en a pas besoin.

Java 8 a introduit la notion de compact profiles (JSR 337) qui définit trois sous-ensembles des API du runtime. Le fait de n'avoir que trois ensembles rend la solution rigide et ne permet donc pas personnaliser les API requises dans le runtime.

L'API Java Core a donc besoin d'être modularisée.

 

33.2.2. Les limitations du format jar

Le modèle de packaging reposant sur les fichiers de type JAR présente plusieurs inconvénients :

  • le nom du fichier JAR n'est pas utilisé par la JVM
  • un JAR ne permet pas de déclarer ses dépendances
  • aucun mécanisme de gestion de versions n'est proposé

Ces inconvénients conduisent à différentes problématiques :

  • il est impossible pour la JVM de déterminer si tous les JAR requis sont présents dans le classpath. Ainsi si une classe n'est pas trouvée, une exception de type NoClassDefFoundError est levée à sa première utilisation
  • un fichier JAR est un simple conteneur : il n'est pas possible de mettre en oeuvre l'encapsulation entre JAR
  • des conflits de versions si plusieurs versions d'un jar sont présents dans le classpath

 

33.2.3. Les problématiques liées au classpath

Il existe trois classpath :

  • Boot Classpath : permet de charger des classes du rt.jar
  • Extension Classpath : permet de charger des classes fournies dans le mécanisme d'extension
  • User ClassPath : permet de charger des classes de l'application. C'est le classpath le mieux connu car c'est celui que l'on configure couramment. Par défaut, c'est le répertoire courant. Il est possible de le configurer en utilisant l'option -classpath ou -cp : la valeur peut être composée de répertoires et/ou de fichiers .jar

Chacun est utilisé par un ClassLoader dédié. Toutes les classes requises sont chargées par un ClassLoader. Les ClassLoader proposés en standard dans la JVM appliquent le principe de délégation pour demander à son ClassLoader parent de tenter de charger la classe demandée.

Ce mécanisme de délégation améliore la sécurité (par exemple pour permettre de charger la classe java.lang.String du fichier rt.jar et pas d'un jar de l'application). Par contre, ce mécanisme de délégation ralentit le temps de chargement d'une classe. C'est d'autant plus important qu'une application peut charger des centaines voire des milliers de classes.

Un de ces ClassLoader peut charger des classes indiquées dans le classpath. Par défaut, toutes classes de l'application sont chargées par le même ClassLoader à partir des composants précisés dans le classpath. Il est possible d'utiliser d'autres ClassLoader ce qui rend le mécanisme de chargement de classes encore plus complexe.

Le classpath ne permet pas d'exprimer les relations entre les composants. Par conséquent, si un composant nécessaire est manquant, il ne sera pas détecté jusqu'à ce qu'il soit tenté de l'utiliser. Le classpath permet également de charger des classes d'un même package à partir de différents composants, ce qui entraîne un comportement imprévisible et des erreurs difficiles à diagnostiquer.

Comme il n'y aucune information sur les dépendances requises et que les classes sont chargées uniquement lorsque la JVM en a besoin, l'application peut démarrer et des soucis peuvent survenir en cours d'exécution.

La plateforme Java ne propose aucune fonctionnalité pour déterminer si les bons fichiers JAR sont disponibles. Le compilateur et la JVM repose sur le mécanisme du classpath. Ce comportement n'est pas fiable et conduit parfois à des comportements inattendus.

 

33.2.3.1. JAR/Classpath Hell

Le mécanisme de classpath, introduit depuis Java 1.0, implique plusieurs soucis :

  • impossibilité de savoir au lancement d'une application si un jar est manquant puisque c'est uniquement lorsqu'une classe sera utilisée qu'elle sera chargée
  • quelle classe est chargée si elle est présente en plusieurs versions dans le classpath
  • quel est l'ordre de parcours des éléments du classpath
  • une classe peut être chargée par différents ClassLoader

Ces problématiques liées à l'utilisation du classpath sont connues sous le nom de Classpath Hell ou JAR Hell.

Le terme JAR Hell ou Classpath Hell désigne des problèmes induits par le mécanisme de chargement des classes issues du classpath. Ce mécanisme de chargement des classes de la JVM via le classpath est à l'origine de plusieurs problèmes.

 

33.2.3.2. Un classpath pour le compilateur et un autre pour la JVM

Le mécanisme de classpath est utilisé lors de étapes de compilation et d'exécution. Il y a donc deux classpath :

  • un pour le compilateur
  • un pour la JVM à l'exécution de l'application

Il est possible que ces deux classpath soit différents puisqu'ils doivent explicitement être définis pour le compilateur à la compilation et pour la JVM à l'exécution. Il est ainsi possible de la compilation réussisse car tous les éléments requis sont dans le classpath mais qu'un élément soit absent du classpath de la JVM à l'exécution et lève une exception de type NoClassDefFoundError ou ClassNotFoundException.

Bien sûr à la compilation, si une dépendance est absente, le compilateur émet une erreur et le code n'est pas compilé.

Le code compilé ne signifie pas que tout va bien se passer à l'exécution. Comme la JVM possède son propre classpath, il est possible d'avoir des erreurs à l'exécution si la classe est absente ou si la version de la classe est différente de celle utilisée à la compilation.

 

33.2.3.3. Les collisions de versions

Il est possible que le classpath contienne plusieurs versions différentes d'un même jar : le classLoader charge alors la première version de la classe trouvée, ce qui peut provoquer des comportements erratiques.

Il est possible d'avoir plusieurs fois la même classe (possédant le même nom pleinement qualifié) dans différents éléments du classpath. C'est fréquent notamment lorsque le classpath contient plusieurs versions différentes d'une même bibliothèque à cause des dépendances transitives par exemple.

Le mécanisme de chargement de classes à partir du classpath charge la première classe trouvée, rendant impossible le chargement d'une autre version de la classe contenue dans le classpath (le terme anglais pour désigner ce comportement est Shadowing). Il n'y a aucune spécification sur l'ordre de chargement de classes donc aucune garantie sur la version de la classe qui sera chargée même si généralement les JVM scannent les JAR dans l'ordre dans lequel ils sont fournis dans le classpath. Cela peut ne pas être le cas sur toutes les JVM ni dans le cas où le caractère joker * ou des répertoires sont utilisés dans le classpath.

Cette problématique courante avec le classpath survient lorsque deux jars ont une dépendance vers deux versions différentes d'un autre jar. Il est ainsi possible qu'une même classe soient dans plusieurs jars inclus dans le classpath puisque plusieurs versions d'un jar sont dans le classpath.

Si les deux versions du jar sont ajoutées dans le classpath alors le comportement risque de ne pas être celui attendu. Une même classe ne peut être chargée qu'une seule fois par un même ClassLoader : c'est la première trouvée par le ClassLoader qui sera utilisée. La seconde version de la classe présente dans le classpath ne sera pas chargée. Dans le meilleur des cas, la version la plus récente est compatible avec la version précédente et il suffit simplement d'ajouter uniquement la version la plus récente dans le classpath. Ceci n'est vrai que s'il y a une compatibilité ascendante, ce qui n'est pas toujours le cas.

La version de la classe chargée n'est donc pas prédictible de manière fiable. Si les différences entre les versions d'une même classe ne concernent que le comportement, cela peut engendrer des bugs aléatoires difficilement identifiables induisant des comportements inattendus. La détection de ces problèmes n'est pas toujours facile.

 

33.2.3.4. Le temps de démarrage d'une JVM

Certains mécanismes liés au classpath ralentissent les performances notamment pour :

  • le parcours séquentiel des jars du classpath pour trouver une classe
  • le parcours des classes pour rechercher quelles sont celles possédant une annotation particulière

 

33.2.4. L'impossibilité de définir les dépendances

Depuis la première version de Java, une application exécutée dans une JVM ne connait pas ses dépendances. Un JAR ne permet pas de définir quels sont les autres JAR dont il dépend ce qui permettrait à la JVM de s'assurer sur tous les composants nécessaires sont présents dans le classpath.

Le problème se complexifie encore avec les dépendances transitives : ce sont les dépendances requises par une dépendance. Ces dépendances de dépendances peuvent elles-mêmes requérirent d'autres dépendances et ainsi de suite. Les dépendances transitives rendent la gestion des dépendances encore plus complexe et augmente donc les possibilités d'erreurs. C'est notamment le cas lors de la discordance de versions entre des jar dépendants.

Il est donc nécessaire de s'assurer manuellement que tous les jars utiles et nécessaire sont dans le classpath. C'est aussi le cas pour les dépendances optionnelles : ce sont des jars dont les classes ne sont nécessaires que selon l'utilisation de certaines fonctionnalités.

Des outils de build externes au JDK peuvent aider à gérer ces dépendances comme par exemple Maven de la fondation Apache. Cela ne résout toujours pas tous les problèmes : il est possible qu'il manque des composants dans le classpath.

En l'absence de configuration, il est courant d'avoir des NoClassDefFoundError au runtime, et ce bien après le démarrage de l'application. Ceci est dû au fait que les classes ne sont chargées que lorsque la JVM en a besoin et qu'il est possible que des dépendances soient manquantes.

La JVM ne peut détecter l'absence d'une classe dans le classpath qu'au moment où elle est requise et doit être chargée. Si la classe ne peut être chargée par la JVM car elle n'est pas trouvée dans le classpath, elle lève une exception de type NoClassDefFoundError ou ClassNotFoundException en cas de tentative de chargement par introspection (méthode forName() de la classe Class ou loadClass() et findSystemClass() de la classe ClassLoader).

 

33.2.5. Pas d'encapsulation dans un jar ou entre les jars

Il n'est pas possible d'avoir des éléments visibles, par exemple uniquement par ceux du fichier JAR qui les contiennent, ou en dehors de celui-ci. Le mécanisme de contrôle d'accès du langage de programmation Java et de la machine virtuelle Java ne permet à aucun composant d'empêcher d'autres composants d'accéder à ses packages.

Historiquement, l'encapsulation est uniquement proposée au travers des modificateurs de visibilité. Les modificateurs de visibilité de Java peuvent être utilisés pour implémenter l'encapsulation entre les classes d'un même package. Mais entre plusieurs packages, il n'y a que la visibilité publique qui soit utilisable.

Toutes les classes publiques sont accessibles par toutes les autres du classpath. Cela limite les possibilités d'encapsulation notamment au niveau du JAR.

En conséquence de nombreuses classes de l'API Core de Java sont public mais ne devrait pas être utilisée. Mais comme elles sont publiques, il est possible de les utiliser directement ou indirectement si c'est une dépendance qui les utilise. C'est notamment le cas des classes des packages sun.misc, jdk.internal, ... La plus connues d'entre-elles est la classe sun.misc.Unsafe. Malgré son nom explicite, elle est largement utilisée notamment par des frameworks couramment utilisés.

Les API internes du JDK, principalement dans les packages sun.* sont encapsulées dans des modules qui ne les exportent pas : ces API ne sont donc plus utilisables.

La vocation de ces API n'était pas d'être utilisable en dehors du JDK mais comme elles étaient public et proposaient des fonctionnalités puissantes, elles ont été largement utilisées par des bibliothèques.

Certaines de ces API ont été remplacées et certaines sont encore accessibles pour le moment mais elles devront être retirées à terme.

 

33.2.6. La sécurité

Pour permettre une utilisation d'une classe par une classe d'un autre package, celle-ci doit être public. De fait, n'importe qu'elle autre classe du classpath peut l'utiliser : cela peut induire des problèmes de sécurité lié au fait que du code malicieux puisse invoquer des fonctionnalités critiques.

Pour compenser cela, Java 1.1 a introduit le SecurityManager dont le rôle est de vérifier si l'utilisation d'une fonctionnalité critique est autorisée ou non. Bien sûre pour que cela fonctionne, il est nécessaire que le SecurityManager soit invoqué dans le code pour vérifier l'autorisation de l'invocation d'une fonctionnalité.

De plus, en utilisant l'API Introspection, il est facile d'accéder à des membres privés même pour les modifier dans le cas de propriétés.

 

33.3. Java Plateform Module System (JPMS)

Un système de modules Java doit faciliter la séparation du code en modules distinct, respectant une encapsulation stricte et faiblement couplés. Les dépendances doivent être clairement spécifiées et strictement appliquées.

Ainsi, JPMS permet de structurer le code de manière modulaire en proposant une encapsulation forte et une configuration plus fiable, tout en permettant un couplage faible entre modules.

Le système de modules est utilisé dans le JDK lui-même et peut être utilisé dans les applications.

JPMS impacte de manière profond la plateforme, les outils, les applications et l'ensemble de l'écosystème Java. JPMS est probablement la première fonctionnalité introduite en Java qui a un impact sur toute la stack : JVM, Java Core, outils (compilateur, packaging, build, ...), bibliothèques, applications, ...

En plus des améliorations pour la plate-forme elle-même, le système de modules change complètement la façon dont les applications Java vont être structurées. L'un des objectifs est de renforcer l'encapsulation : avec les modules, il est possible de définir précisément les éléments qui seront utilisables pour les autres modules. Et pour la première fois, du code Java aura la possibilité de connaître ses dépendances et d'avoir une configuration plus fiable. Ensemble, ces fonctionnalités tentent de trouver partiellement une solution au Classpath Hell.

JPMS améliore la sécurité et facilite la maintenance des applications et des bibliothèques grâce à l'encapsulation forte et une meilleure fiabilité de la configuration.

JPMS change de manière importante comment les applications, notamment les grosses, sont développées et surtout déployées.

 

33.3.1. Les buts de JPMS

Les principaux objectifs de la modularisation proposée par JPMS sont :

  • encapsulation forte (strong encapsulation) : JPMS permet à un module de déclarer lesquels de ses packages sont accessibles par d'autres modules, car par défaut ils ne le sont pas. Cette encapsulation est appliquée aussi dans les modules du JDK pour empêcher l'accès à leurs API internes
  • configuration fiable (reliable configuration) : JPMS permet à un module de déclarer qu'il dépend d'autres modules, comme d'autres modules peuvent en dépendre. En utilisant la configuration de chaque module, le compilateur et la JVM peuvent d'assurer que tous les modules requis sont présents. Les mécanismes des modules est ainsi plus fiable que le mécanisme utilisé par le classpath. Si un jar en manquant dans le classpath à l'exécution, il ne sera détecté de lors du chargement d'une de ces classes. Ce chargement n'est effectué que lors de la première utilisation de la classe : cela peut survenir bien après le démarrage de l'application
  • amélioration des performances notamment lors du chargement des classes : une classe peut être chargée plus rapidement grâce au fait que son package ne peut être que dans un seul module
  • une plate-forme avec taille adaptable : JPMS permet à la plate-forme Java SE et à ses implémentations d'être décomposées en un ensemble de composants qui peuvent être assemblés par les développeurs dans des configurations personnalisées qui contiennent uniquement les fonctionnalités réellement requises par une application. Cette réduction de l'empreinte du JRE facilite notamment l'utilisation pour des applications embarquées (IoT) et le déploiement dans le cloud

Tous ces objectifs n'ont pas une importance équivalente pour le JDK et pour les applications.

 

33.3.2. Une configuration plus fiable (reliable configuration)

Chaque module est identifié par un nom défini dans un descripteur de module. Ce descripteur de module doit aussi contenir la liste des modules requis en tant que dépendance. Cette configuration pour être utilisée à différentes étapes :

  • compilation
  • build
  • exécution

Ces différentes étapes pourront alors échouer si une dépendance est manquante ou si une anomalie est détectée, et ce dès leurs premières étapes.

 

33.3.3. L'encapsulation forte (strong encapsulation)

Un des intérêts des modules est de permettre de renforcer l'encapsulation. Par défaut, aucune classe d'un module n'est accessible en dehors du module même si la classe est public. Pour permettre un accès à d'autres modules, il est nécessaire d'exporter le ou les packages concernés. Tous les autres packages non exportés ne sont accessibles uniquement que par le module lui-même.

Une fois un package exporté, les règles de visibilité d'une classe s'applique comme depuis toujours en Java.

Un module ajoute donc en plus de la visibilité un niveau d'accessibilité.

Le sens du modificateur public change dans un module. Par défaut, une classe publique dans un module n'est accessible que par les autres classes du module. Pour que cette classe publique puisse être utilisé dans d'autre module, il fait obligatoire exporter le package qui contient cette classe. Toutes les classes publiques d'un package exporté sont alors potentiellement accessibles par d'autres modules qui déclareront le module en dépendance.

Ainsi pour permettre l'utilisation d'une classe d'un module par un autre module, trois contraintes doivent être respectées :

  • la classe doit être public
  • le package contenant la classe doit être exporté
  • le module qui souhaite utiliser la classe doit déclarer le module de la classe en tant que dépendance

La mise en oeuvre de ces trois règles permet un accès à une classe par un autre module.

Le fait de ne pas permettre par défaut un accès aux classes publiques d'un package permet de renforcer l'encapsulation. Il est par exemple possible de ne pas exposer des packages qui contiennent une implémentation et d'exposer les packages des interfaces.

L'encapsulation forte des modules va renforcer la sécurité et la maintenabilité :

  • le code critique peut être encapsulé dans le module et rendu non visible même si cela concerne une classe public
  • l'API publique d'un module peut être réduit à son minimum
  • une application concrète est mise en oeuvre dans le JDK : les API internes non standard sont encapsulées et ne sont donc plus accessible par les applications

 

33.3.4. L'évolutivité de la plateforme

La modularisation a été appliquée à Java Core : l'historique fichier rt.jar a été découpé en un certain nombre de modules. La maintenance d'éléments plus petits est plus simple que la maintenance d'un élément monolithique.

Les modules facilitent aussi l'ajout ou la suppression de fonctionnalités dans Java Core.

Avec la modularisation, il est possible de créer son propre JRE personnalisé composé uniquement des modules dont une application a besoin. Cela contribue à renforcer la plate-forme Java comme une solution pour des applications sur petits périphériques ou dans des conteneurs.

 

33.4. L'implémentation du système de modules

Le système de modules a été développé dans le projet Jigsaw http://jdk9.java.net/jigsaw

La JSR 376 (Java Platform Module system) spécifie les évolutions dans le langage Java, la JVM et l'API Java pour la mise en oeuvre du système de modules dans la plateforme Java.

La mise en oeuvre du système de modules repose sur plusieurs JEP.

 

33.4.1. Le projet Jigsaw

Le rôle du projet Jigsaw est de permettre la mise en oeuvre d'un système de modules pour la plateforme Java nommé Java Platform Module System (JPMS).

Le but initial était de rendre la plateforme Java SE modulaire puis a été étendu pour permettre d'utiliser ce système pour rendre modulaire le JRE mais aussi les applications Java.

Les principaux buts du projet Jigsaw sont de définir un système standard de modules pour la plateforme Java et le langage Java afin de permettre :

  • d'améliorer la sécurité et la maintenabilité
  • d'améliorer la scalabilité et les performances
  • de permettre l'exécution sur des environnements à ressource réduites

Pour atteindre ces objectifs, le projet a conçu et implémenté un système de modules qui a été appliqué à la plateforme et au JDK au travers de différentes actions :

  • créer un système de modules : implémenté dans la JEP 261
  • appliquer ce système de modules aux sources du JDK : implémenté dans la JEP 201
  • modulariser les bibliothèques du JDK : implémenté dans la JEP 200
  • faire évoluer la plateforme pour offrir un support des modules : implémenté dans la JEP 220
  • permettre de créer une plateforme de plus petite taille en intégrant que le sous-ensemble de modules requis : implémenté dans la JEP 282

L'intégration du projet Jigsaw a été initialement prévue pour Java 7, repoussée à Java 8, pour finalement être décalée à nouveau pour finalement être intégrée à Java 9. La première mouture du système de module spécifié par la JSR 376 et implémenté par la JEP 261 a été intégré dans le build 111 du JDK 9 en mars 2016.

 

33.4.2. Les JEP utilisées pour intégrer le système de modules

Le système de modules a été développé au travers de plusieurs JEP :

JEP

Rôle

JEP 261: Module System

Implémenter le Java Platform Module System (JPMS) tel que spécifié dans la JSR 376

JEP 200: The Modular JDK

Modulariser la plateforme Java tel que précisé dans la JSR 376 et implémenté dans la JEP 261 : définit la structure modulaire du JDK

JEP 220: Modular Run-Time Images

Décomposer le JDK et le JRE en images pour chaque module

Définir un nouveau format des URI pour nommer les modules, les classes et les ressources de manière indépendante du format de l'image

Suppression des fichiers rt.jar et tools.jar du JRE

Suppression des mécanismes endorsed et extension

JEP 260: Encapsulate Most Internal APIs

Encapsuler la plupart des API à usage interne du JDK sauf celles qui sont largement utilisés et ne possèdent pas encore de solution de remplacement

JEP 201: Modular Source Code

Réorganiser le code source du JDK pour le rendre modulaire

JEP 282: jlink the Java Linker

Fournir un outil capable de créer un JRE personnalisé pour une application

Créer un outil qui permet d'assembler et optimiser un ensemble de modules et leurs dépendances afin d'obtenir une image (jlink)

 

33.4.3. La modularisation du JDK

A partir de Java 9, l'API Core de Java est obligatoirement modulaire. Le JDK lui-même a été modularisé pour être composé d'un peu moins d'une centaine de modules désignés comme étant des platform modules.

Il est possible d'obtenir le liste complète des platform modules en utilisant l'option --list-modules de la JVM

Résultat :
$ java --list-modules | wc -l
98

Le système de modules fait la distinction entre les modules standard et les modules non standard. Les modules standard ont leurs spécifications gérées par la plateforme Java et leurs noms de modules commencent par "java.".

Les modules non standard ne devraient donc pas avoir leur nom commençant par "java.".

À la racine de l'arborescence des modules se trouve le module java.base, qui contient les classes essentielles notamment celles des packages java.lang, java.io, java.math, java.net, java.nio, java.security, java.text, java.time, java.util, ...

Les classes dans le classpath ont accès à l'intégralité des classes contenues dans les modules du JDK pour des raisons évidentes de compatibilité.

Les modules dans le module path doivent explicitement déclarer leur dépendance vers les modules requis y compris ceux du JDK.

La structure du JDK a été impactée :

  • Le fichier rt.jar n'existe plus
  • Les modules sont packagés en utilisant le format jmod
  • Les mécanismes endorsed et d'extension sont supprimés

La structure de sous-répertoires du JDK contient un répertoire nommé jmods. Ce répertoire contient des fichiers avec l'extension .jmod, un pour chaque module. Un fichier jmod contient des classes regroupées en packages, des ressources et des bibliothèques natives.

Le fichier src.zip contient toujours les sources de l'API Core de Java : il est dans le sous-répertoire lib du JDK. Il contient un sous-répertoire pour chaque module du JDK.

 

33.4.4. Les modules dépréciés de Java 9

Plusieurs modules de Java 9 sont dépréciés avec l'attribut forRemoval=true :

  • java.activation
  • java.corba
  • java.transaction
  • java.xml.bind
  • java.xml.ws
  • java.xml.ws.annotation

Ces modules sont fournis dans Java 9 mais ne sont pas accessibles par défaut. Il faut explicitement les ajoutées en tant que module racine du graphe de modules. Tous ces modules sont retirés dans Java 11.

 

33.4.5. Les modules du JDK

Les modules de la plateforme sont repartis en plusieurs groupes. Le groupe sert de préfixe dans le nom de chaque module :

  • java : les modules standard
  • javafx : les modules de JavaFX
  • jdk : les modules utilisés pour les outils fournis dans le JDK
  • oracle : les modules spécifiques à Oracle

Tous les modules de la plateforme ou applicatifs dépendent implicitement du module java.base. Le module java.base contient les classes et API de base (utils, collections, IO, concurrency, ...)

Le module java.base est le module de base : c'est un module particulier qui ne dépend d'aucun autre module. Tous les autres modules dépendent implicitement du module java.base. Le module java.base expose de nombreux packages notamment :

  • java.io
  • java.lang.*
  • java.math
  • java.net.*
  • java.nio.*
  • java.security.*
  • java.text.*
  • java.time.*
  • java.util.*
  • javax.crypto.*
  • javax.net.*
  • javax.security.*

Des modules dédiés existent pour les autres fonctionnalités logging, desktop, xml, sql, naming, corba, ...

Il est possible d'utiliser l'option --list-modules de la JVM pour obtenir la liste des modules.

Résultat :
$ java --list-modules
java.activation@9.0.1
java.base@9.0.1
java.compiler@9.0.1
java.corba@9.0.1
java.datatransfer@9.0.1
java.desktop@9.0.1
...

Le nom de chaque module se termine par un @ suivi du numéro de version du module.

Les modules du JDK varient en fonction de la version de Java utilisée soit parce que des fonctionnalités sont ajoutées ou retirées :

Module

Versions de Java

9

<²p>10

11

12

13

14

15

16

17

18

java.activation

*

*

 

 

 

 

 

 

 

 

java.base

*

*

*

*

*

*

*

*

*

*

java.compiler

*

*

*

*

*

*

*

*

*

*

java.corba

*

*

 

 

 

 

 

 

 

 

java.datatransfer

*

*

*

*

*

*

*

*

*

*

java.desktop

*

*

*

*

*

*

*

*

*

*

java.instrument

*

*

*

*

*

*

*

*

*

*

java.jnlp

*

*

 

 

 

 

 

 

 

 

java.logging

*

*

*

*

*

*

*

*

*

*

java.management

*

*

*

*

*

*

*

*

*

*

java.management.rmi

*

*

*

*

*

*

*

*

*

*

java.naming

*

*

*

*

*

*

*

*

*

*

java.net.http

 

 

*

*

*

*

*

*

*

*

java.prefs

*

*

*

*

*

*

*

*

*

*

java.rmi

*

*

*

*

*

*

*

*

*

*

java.scripting

*

*

*

*

*

*

*

*

*

*

java.se

*

*

*

*

*

*

*

*

*

*

java.se.ee

*

*

 

 

 

 

 

 

 

 

java.security.jgss

*

*

*

*

*

*

*

*

*

*

java.security.sasl

*

*

*

*

*

*

*

*

*

*

java.smartcardio

*

*

*

*

*

*

*

*

*

*

java.sql

*

*

*

*

*

*

*

*

*

*

java.sql.rowset

*

*

*

*

*

*

*

*

*

*

java.transaction

*

*

 

 

 

 

 

 

 

 

java.transaction.xa

 

 

*

*

*

*

*

*

*

*

java.xml

*

*

*

*

*

*

*

*

*

*

java.xml.bind

*

*

 

 

 

 

 

 

 

 

java.xml.crypto

*

*

*

*

*

*

*

*

*

*

java.xml.ws

*

*

 

 

 

 

 

 

 

 

java.xml.ws.annotation

*

*

 

 

 

 

 

 

 

 

javafx.base

*

*

 

 

 

 

 

 

 

 

javafx.controls

*

*

 

 

 

 

 

 

 

 

javafx.deploy

*

*

 

 

 

 

 

 

 

 

javafx.fxml

*

*

 

 

 

 

 

 

 

 

javafx.graphics

*

*

 

 

 

 

 

 

 

 

javafx.media

*

*

 

 

 

 

 

 

 

 

javafx.swing

*

*

 

 

 

 

 

 

 

 

javafx.web

*

*

 

 

 

 

 

 

 

 

jdk.accessibility

*

*

*

*

*

*

*

*

*

*

jdk.aot

 

*

*

*

*

*

*

*

 

 

jdk.attach

*

*

*

*

*

*

*

*

*

*

jdk.charsets

*

*

*

*

*

*

*

*

*

*

jdk.compiler

*

*

*

*

*

*

*

*

*

*

jdk.crypto.cryptoki

*

*

*

*

*

*

*

*

*

*

jdk.crypto.ec

*

*

*

*

*

*

*

*

*

*

jdk.crypto.mscapi

*

*

*

*

*

*

*

*

*

*

jdk.deploy

*

*

 

 

 

 

 

 

 

 

jdk.deploy.controlpanel

*

*

 

 

 

 

 

 

 

 

jdk.dynalink

*

*

*

*

*

*

*

*

*

*

jdk.editpad

*

*

*

*

*

*

*

*

*

*

jdk.hotspot.agent

*

*

*

*

*

*

*

*

*

*

jdk.httpserver

*

*

*

*

*

*

*

*

*

*

jdk.incubator.foreign

 

 

 

 

 

 

*

*

*

*

jdk.incubator.jpackage

 

 

 

 

 

 

*

 

 

 

jdk.incubator.vector

 

 

 

 

 

 

 

*

*

*

jdk.incubator.httpclient

*

*

 

 

 

 

 

 

 

 

jdk.internal.ed

*

*

*

*

*

*

*

*

*

*

jdk.internal.jvmstat

*

*

*

*

*

*

*

*

*

*

jdk.internal.le

*

*

*

*

*

*

*

*

*

*

jdk.internal.opt

*

*

*

*

*

*

*

*

*

*

jdk.internal.vm.ci

*

*

*

*

*

*

*

*

*

*

jdk.internal.vm.compiler

 

*

*

*

*

*

*

*

*

*

jdk.internal.vm.compiler.management

 

*

*

*

*

*

*

*

*

*

jdk.jartool

*

*

*

*

*

*

*

*

*

*

jdk.javadoc

*

*

*

*

*

*

*

*

*

*

jdk.javaws

*

*

 

 

 

 

 

 

 

 

jdk.jcmd

*

*

*

*

*

*

*

*

*

*

jdk.jconsole

*

*

*

*

*

*

*

*

*

*

jdk.jdeps

*

*

*

*

*

*

*

*

*

*

jdk.jdi

*

*

*

*

*

*

*

*

*

*

jdk.jdwp.agent

*

*

*

*

*

*

*

*

*

*

jdk.jfr

*

*

*

*

*

*

*

*

*

*

jdk.jlink

*

*

*

*

*

*

*

*

*

*

jdk.jpackage

 

 

 

 

 

 

 

*

*

*

jdk.jshell

*

*

*

*

*

*

*

*

*

*

jdk.jsobject

*

*

*

*

*

*

*

*

*

*

jdk.jstatd

*

*

*

*

*

*

*

*

*

*

jdk.localedata

*

*

*

*

*

*

*

*

*

*

jdk.management

*

*

*

*

*

*

*

*

*

*

jdk.management.agent

*

*

*

*

*

*

*

*

*

*

jdk.management.cmm

*

*

 

 

 

 

 

 

 

 

jdk.management.jfr

*

*

*

*

*

*

*

*

*

*

jdk.management.resource

*

*

 

 

 

 

 

 

 

 

jdk.naming.dns

*

*

*

*

*

*

*

*

*

*

jdk.naming.rmi

*

*

*

*

*

*

*

*

*

*

jdk.net

*

*

*

*

*

*

*

*

*

*

jdk.nio.mapmode

 

 

 

 

 

*

*

*

*

*

jdk.pack

*

*

*

*

*

 

 

 

 

 

jdk.packager

*

*

 

 

 

 

 

 

 

 

jdk.packager.services

*

*

 

 

 

 

 

 

 

 

jdk.plugin

*

*

 

 

 

 

 

 

 

 

jdk.plugin.dom

*

 

 

 

 

 

 

 

 

 

jdk.plugin.server

*

*

 

 

 

 

 

 

 

 

jdk.policytool

*

 

 

 

 

 

 

 

 

 

jdk.rmic

*

*

*

*

*

*

 

 

 

 

jdk.random

 

 

 

 

 

 

 

 

*

*

jdk.scripting.nashorn

*

*

*

*

*

*

 

 

 

 

jdk.scripting.nashorn.shell

*

*

*

*

*

*

 

 

 

 

jdk.sctp

*

*

*

*

*

*

*

*

*

*

jdk.security.auth

*

*

*

*

*

*

*

*

*

*

jdk.security.jgss

*

*

*

*

*

*

*

*

*

*

jdk.snmp

*

*

 

 

 

 

 

 

 

 

jdk.unsupported

*

*

*

*

*

*

*

*

*

*

jdk.unsupported.desktop

 

 

*

*

*

*

*

*

*

*

jdk.xml.bind

*

*

 

 

 

 

 

 

 

 

jdk.xml.dom

*

*

*

*

*

*

*

*

*

*

jdk.xml.ws

*

*

 

 

 

 

 

 

 

 

jdk.zipfs

*

*

*

*

*

*

*

*

*

*

oracle.desktop

*

*

 

 

 

 

 

 

 

 

oracle.net

*

*

 

 

 

 

 

 

 

 

 

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

78 commentaires Donner une note à l´article (5)

 

Copyright (C) 1999-2022 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.