Modularité en Java 9

La principale innovation de Java 9 a été l'introduction de la modularité. Il y a eu beaucoup de discussions sur cette fonctionnalité, la date de sortie a été reportée plusieurs fois pour tout finir correctement. Dans cet article, nous parlerons de ce qui donne le mécanisme des modules et de l'utilité de Java 9 en général. La base de ce poste était le rapport de mon collègue Sergei Malkevich .




Pour implémenter les modules dans cette version de Java, un projet entier a été alloué - Project Jigsaw - qui comprend plusieurs JEP et JSR.



Pour les fans de documentation officielle, vous pouvez en savoir plus sur chaque JEP ici .

Plus sur Project Jigsaw


Le projet Jigsaw, qui met en œuvre la modularité, a commencé à être développé en 2005: le premier JSR 277 a été publié, et déjà en 2008, le travail direct sur le projet a commencé. La libération n'a eu lieu qu'en 2017. Autrement dit, pour visser les modules en Java, il a fallu près de 10 ans. Ce qui, en fait, met l'accent sur la pleine échelle du travail et les changements qui ont été apportés lors de la mise en œuvre de la modularité.

Quels sont les objectifs fixés par les développeurs:

  • faciliter le développement de grandes applications et bibliothèques;
  • Améliorer la sécurité de Java SE en général et de JDK en particulier;
  • augmenter les performances des applications;
  • créer la possibilité de réduire la taille du JRE pour qu'il s'exécute sur de petits appareils afin de ne pas consommer trop de mémoire;
  • JAR HELL (plus à ce sujet plus tard).

Quel Java 9 utile


Avant la version 9, JDK et JRE étaient monolithiques. Leur taille augmentait à chaque sortie. Java 8 occupait déjà des centaines de mégaoctets, et tout cela, les développeurs devaient «l'emporter avec eux» à chaque fois pour pouvoir exécuter des applications Java. Seul rt.jar pèse à lui seul environ 60 Mo. Eh bien, ici, nous ajoutons également un démarrage lent et une consommation de mémoire élevée. Java 9 est venu à la rescousse.

Dans JDK 9la séparation des modules a été introduite, à savoir, le JDK a été divisé en 73 modules. Et à chaque nouvelle version, le nombre de ces modules augmente. Dans la version 11, ce nombre est proche de 100. Cette séparation a permis aux développeurs de créer l'utilitaire JLINK. À l'aide de JLINK, vous pouvez créer des ensembles JRE personnalisés qui n'incluront que les modules «nécessaires» dont votre application a réellement besoin. Ainsi, une application simple et certains customJRE avec un ensemble minimal (ou petit) de modules peuvent éventuellement tenir dans 20 Mo, ce qui est une bonne nouvelle.

La liste des modules se trouve ici .

Avec l'avènement de Java 9, la structure JDK a changé: elle est désormais identique à la structure JRE. Si auparavant le JDK incluait le dossier JRE, où bin est à nouveau et les fichiers sont dupliqués, maintenant tout ressemble à ceci:



Modules


Réellement. Qu'est-ce qu'un module? Un module est un nouveau niveau d'agrégation de packages et de ressources (orig. "Un groupe unique et réutilisable de packages liés, ainsi que des ressources et un descripteur de module" ).

Les modules sont livrés dans des fichiers JAR avec des packages et un descripteur de module
module-info.java . Le fichier module-info.java contient une description du module:
nom, dépendances, packages exportés, services consommés et fournis, autorisations d'accès à la réflexion.

Exemples de descriptions de descripteurs de module:

module java.sql {
    requires transitive java.logging;
    requires transitive java.transaction.xa;
    requires transitive java.xml;

    exports java.sql;
    exports javax.sql;

    uses java.sql.Driver;
}

module jdk.javadoc {
   requires java.xml;
   
   requires transitive java.compiler;
   requires transitive jdk.compiler;
   
   exports jdk.javadoc.doclet;
   
   provides java.util.spi.ToolProvider with
       jdk.javadoc.internal.tool.JavadocToolProvider;
   
   provides javax.tools.DocumentationTool with
       jdk.javadoc.internal.api.JavadocTool;
   
   provides javax.tools.Tool with
      jdk.javadoc.internal.api.JavadocTool;   
}

Après le module de mots-clés, nous avons le nom du package jdk.javadoc , qui dépend d'un autre package java.xml et dépend de manière transitoire d'autres packages.

Examinons de plus près chacun des mots clés:

  • require indique les modules dont dépend le module actuel;

  • nécessite transitif - une dépendance transitive - signifie ce qui suit: si le module m1 est transitoirement dépendant du module m2 , et que nous avons un troisième module mX , qui dépend de m1 - le module mX aura également accès à m2 ;

  • requiert statique vous permet de spécifier les dépendances au moment de la compilation;

  • exports , ( “”);

  • exports...to… : export com.my.package.name to com.specific.package; - () () ;

  • uses , :

    uses java.sql.Driver;

    , ;

  • provides , :

    provides javax.tools.Tool with
        jdk.javadoc.internal.api.JavadocTool;

    javax.tools.Tool, with — .

Un peu sur les services.

Disons que nous avons plusieurs modules connectés qui implémentent un service abstrait - MyService . Lors de la création de l'application, nous avons la possibilité de décider quelle implémentation de service utiliser en «faisant glisser» les modules d'implémentation de service requis vers --module-path :

Iterable<MyService> services = 
        ServiceLoader.load(MyService.class);

Ainsi, l'itérateur renvoyé contient une liste d'implémentations de l'interface MyService. En fait, il contiendra toutes les implémentations trouvées dans les modules trouvés sur --module-path .

Pourquoi, en principe, les services ont-ils été introduits? Ils sont nécessaires pour montrer comment notre code sera utilisé. Autrement dit, il y a un rôle sémantique. De plus, la modularité concerne l'encapsulation et la sécurité, car nous pouvons rendre l'implémentation privée et exclure la possibilité d'accès non autorisé par réflexion.

En outre, l'une des options d'utilisation des services est une implémentation assez simple des plugins. Nous pouvons implémenter l'interface de plugin pour notre application et connecter des modules pour travailler avec eux.

Revenons à la syntaxe de description des modules:

Jusqu'à 9ki, par réflexion, nous avions accès à presque tout et pouvions faire ce que nous voulions et avec ce que nous voulions. Et la 9ème version, comme déjà mentionné, vous permet de vous protéger contre l'accès "illégal" à la réflexion.

Nous pouvons ouvrir complètement le module pour l'accès à la réflexion en déclarant ouvert :

open module my.module {
}

Ou, nous pouvons spécifier tous les packages pour l'accès à la réflexion en déclarant opens :

module my.module {
    opens com.my.coolpackage;
}

Ici , il est également possible d'utiliser ouvre com.my.coolpackage à ... , donnant ainsi accès à la réflexion com.my.coolpackage ensemble du paquet que nous indiquerons après à .

Types de modules


Project Jigsaw classe les modules comme suit:

  • System Modules — Java SE JDK . , java --list-modules.

  • Application Modules — , , ( ), .

  • Automatic Modules — , Java JAR-. , , - . JAR- --module-path Java , JAR-.

  • Unnamed Module — , JAR-, --class-path. Java .

Class-path vs module-path


Avec l'avènement des modules, un nouveau concept est apparu - module-path . En substance, c'est le même chemin de classe , mais pour les modules.

Le lancement d'une application modulaire est le suivant:



En mode de lancement normal, nous spécifions les options et le chemin complet vers la classe principale. Dans le cas où nous voulons travailler avec des modules, nous spécifions également des options et le paramètre -m ou -module , qui indique simplement que nous exécuterons les modules. Autrement dit, nous traduisons automatiquement notre application en mode modulaire. Ensuite, nous indiquons le nom du module et le chemin d'accès à la classe principale à partir du module. En

outre, si en mode normal , nous sommes habitués à travailler avec les -CP et les options --class-chemin, en mode modulaire, nous prescrivons un nouveau paramètre -p et --module-path , qui indique les chemins vers les modules utilisés dans l'application.

Souvent, je rencontre le fait que les développeurs ne passent pas à la version 9+, car ils pensent qu'ils devront travailler avec des modules. Bien qu'en fait, nous pouvons exécuter nos applications dans l'ancien mode, simplement sans écrire de paramètre ni utiliser de modules, mais en utilisant uniquement d'autres nouvelles puces.

Enfer de pot


Je veux également m'attarder en diagonale sur le problème de Jar Hell.



Qu'est-ce que Jar Hell en bref? Par exemple, nous avons une sorte de notre application et cela dépend de la X bibliothèque et la bibliothèque Y . En même temps, ces deux bibliothèques dépendent de la bibliothèque Z , mais de versions différentes: X dépend de la version 1 , Y de la version 2 . Eh bien, si la version 2 est rétrocompatible avec la version 1, aucun problème. Et sinon, il est évident que nous obtenons un conflit de version, c'est-à-dire que la même bibliothèque ne peut pas être chargée en mémoire par le même chargeur de classe.

Comment sortez-vous de cette situation? Il existe des méthodes standard que les développeurs utilisent depuis le tout premier Java, par exemple, exclure , quelqu'un utilise des plugins pour Maven, qui renomment les noms des packages racine de la bibliothèque. Ou, les développeurs recherchent différentes versions de la bibliothèque X pour trouver une option compatible.

Pourquoi suis-je: les premiers prototypes de Jigsaw impliquaient que le module avait une version et permettait le chargement de plusieurs versions via différents ClassLoaders, mais plus tard il a été abandonné. En conséquence, la «balle d'argent», que beaucoup attendaient, n'a pas fonctionné.

Mais, dès la sortie de la boîte, nous étions un peu à l'abri de ces problèmes. Java 9 désactive les packages fractionnés- packages divisés en plusieurs modules. Autrement dit, si nous avons le package com.my.coolpackage dans un module, nous ne pouvons pas l'utiliser dans un autre module au sein de la même application. Lorsque vous démarrez l'application avec des modules contenant les mêmes packages, nous plantons simplement. Cette petite amélioration élimine la possibilité d'un comportement imprévisible lors du téléchargement de packages Split.

De plus, en plus des modules eux-mêmes, il existe également un mécanisme de couche ou couches de scie sauteuse , qui aide également à faire face au problème de l'enfer des pots.

Une couche de scie sauteuse peut être définie comme un système modulaire local. Et ici, il convient de noter que les packages Split mentionnés ci-dessus ne sont interdits que dans le cadre d'une couche Jigsaw. Les modules avec les mêmes packages ont leur place, mais ils doivent appartenir à différentes couches.

Cela ressemble à ceci:



Lorsque l'application démarre, une couche de démarrage est créée , qui comprend des modules de plateforme chargés par Bootstrap, des modules de plateforme supplémentaires chargés par le chargeur de plateforme et nos modules d'application chargés par le chargeur d'application.

A tout moment, nous pouvons y créer nos propres couches et «mettre» des modules de différentes versions et ne pas tomber.

Il y a une excellente discussion détaillée sur YouTube sur le sujet: Échapper à l'enfer des pots avec des couches de puzzle

Conclusion


Le moteur de module de Java 9 nous ouvre de nouvelles possibilités, alors que le support des bibliothèques est assez faible aujourd'hui. Oui, les gens exécutent Spring, Spring Boot et ainsi de suite. Mais la plupart des bibliothèques ne sont pas passées à la pleine utilisation des modules. Apparemment, tous ces changements ont donc été perçus de manière plutôt sceptique par la communauté technique. Les modules nous offrent de nouvelles opportunités, mais la question de la demande reste ouverte.

Et enfin, je propose une sélection de documents sur ce sujet:

Project Jigsaw

JDK Module Summary

Paul Deitel - Comprendre les modules Java 9

baeldung.com - Introduction to Project Jigsaw

Alex Buckley - Développement modulaire avec JDK 9

Evgeny Kozlov - Modules en Java

All Articles