Introduction au projet Jigsaw

Introduction au projet Jigsaw

1. introduction

Project Jigsaw est un projet parapluie avec les nouvelles fonctionnalités visant deux aspects:

  • l'introduction du système de module en langage Java

  • et son implémentation dans les sources JDK et l'exécution Java

Dans cet article, nous allons vous présenter le projet Jigsaw et ses fonctionnalités et enfin le conclure avec une simple application modulaire.

2. La modularité

En termes simples, la modularité est un principe de conception qui nous aide à atteindre:

  • couplage lâche entre les composants

  • contrats clairs et dépendances entre composants

  • implémentation cachée utilisant une encapsulation forte

2.1. Unité de modularité

Vient maintenant la question de savoir quelle est l'unité de modularité? Dans le monde Java, notamment avec OSGi, les JAR étaient considérés comme l’unité de la modularité.

Les JAR ont aidé à regrouper les composants connexes, mais ils ont certaines limitations:

  • contrats et dépendances explicites entre JAR

  • faible encapsulation d'éléments au sein des JAR

2.2. JAR Enfer

Il y avait un autre problème avec JAR - l'enfer JAR. Plusieurs versions des JAR se trouvant sur le chemin de classe, ont entraîné le chargement deClassLoader de la première classe trouvée à partir du JAR, avec des résultats très inattendus.

L'autre problème avec la JVM utilisant le chemin de classe était que la compilation de l'application réussirait, mais que l'application échouera à l'exécution avec lesClassNotFoundException, en raison des JAR manquants sur le chemin de classe au moment de l'exécution.

2.3. Nouvelle unité de modularité

Avec toutes ces limitations, lorsqu'ils ont utilisé JAR comme unité de modularité, les créateurs du langage Java ont proposé une nouvelle construction dans le langage, appelée modules. Et avec cela, un tout nouveau système modulaire est prévu pour Java.

3. Projet Jigsaw

Les principales motivations de ce projet sont:

  • create a module system for the language - implémenté sousJEP 261

  • apply it to the JDK source - implémenté sousJEP 201

  • modularize the JDKlibraries - implémenté sousJEP 200

  • update the runtime to support modularity - implémenté sousJEP 220

  • be able to create smaller runtime with a subset of modules from JDK - implémenté sousJEP 282

Une autre initiative importante consiste à encapsuler les API internes dans le JDK, celles qui sont sous les packagessun.* et d'autres API non standard. Ces API n'ont jamais été conçues pour être utilisées par le public et n'ont jamais été planifiées pour être maintenues. Mais la puissance de ces API a permis aux développeurs Java de les exploiter pour développer différentes bibliothèques, frameworks et outils. Des remplacements ont été fournis pour quelques API internes et les autres ont été déplacés dans des modules internes.

4. Nouveaux outils pour la modularité

  • jdeps - aide à analyser la base de code pour identifier les dépendances sur les API JDK et les JAR tiers. Il mentionne également le nom du module où se trouve l'API JDK. Cela facilite la modularisation de la base de code

  • jdeprscan - aide à analyser la base de code pour l'utilisation de toute API obsolète

  • jlink - aide à créer un environnement d'exécution plus petit en combinant les modules de l'application et du JDK

  • jmod - aide à travailler avec les fichiers jmod. jmod est un nouveau format pour empaqueter les modules. Ce format permet d'inclure du code natif, des fichiers de configuration et d'autres données qui ne rentrent pas dans les fichiers JAR

5. Architecture du système de module

Le système de modules, implémenté dans le langage, les prend en charge en tant que construction de niveau supérieur, tout comme les packages. Les développeurs peuvent organiser leur code en modules et déclarer des dépendances entre eux dans leurs fichiers de définition de module respectifs.

Un fichier de définition de module, nommémodule-info.java, contient:

  • son nom

  • les paquets qu'il met à la disposition du public

  • les modules dont il dépend

  • tous les services qu'il consomme

  • toute mise en œuvre pour le service fourni

Les deux derniers éléments de la liste ci-dessus ne sont pas couramment utilisés. Ils ne sont utilisés que lorsque les services sont fournis et consommés via l'interfacejava.util.ServiceLoader.

Une structure générale du module ressemble à ceci:

src
 |----com.example.reader
 |     |----module-info.java
 |     |----com
 |          |----example
 |               |----reader
 |                    |----Test.java
 |----com.example.writer
      |----module-info.java
           |----com
                |----example
                     |----writer
                          |----AnotherTest.java

L'illustration ci-dessus définit deux modules:com.example.reader etcom.example.writer. Chacun d'eux a sa définition spécifiée enmodule-info.java et les fichiers de code placés souscom/example/reader etcom/example/writer, respectivement.

5.1. Terminologies de définition de module

Examinons quelques-unes des terminologies; nous utiliserons lors de la définition du module (c'est-à-dire dans lesmodule-info.java):

  • module: le fichier de définition du module commence par ce mot-clé suivi de son nom et de sa définition

  • requires: sert à indiquer les modules dont il dépend; un nom de module doit être spécifié après ce mot-clé

  • transitive: est spécifié après le mot-clérequires; cela signifie que tout module qui dépend du module définissantrequires transitive <modulename> obtient une dépendance implicite sur les <modulename>

  • exports: est utilisé pour indiquer les packages du module disponibles publiquement; un nom de package doit être spécifié après ce mot clé

  • opens: est utilisé pour indiquer les packages qui sont accessibles uniquement à l'exécution et également disponibles pour l'introspection via les API Reflection; ceci est assez important pour les bibliothèques comme Spring et Hibernate, qui dépendent fortement des API Reflection; opens peut également être utilisé au niveau du module auquel cas l'ensemble du module est accessible à l'exécution

  • uses: est utilisé pour indiquer l'interface de service que ce module utilise; un nom de type, c'est-à-dire un nom complet de classe / interface, doit être spécifié après ce mot-clé

  • provides … with ..: ils sont utilisés pour indiquer qu'il fournit des implémentations, identifiées après le mot-cléwith, pour l'interface de service identifiée après le mot-cléprovides

6. Application modulaire simple

Créons une application modulaire simple avec les modules et leurs dépendances, comme indiqué dans le schéma ci-dessous:

image

Lecom.example.student.model est le module racine. Il définit la classe de modèlecom.example.student.model.Student, qui contient les propriétés suivantes:

public class Student {
    private String registrationId;
    //other relevant fields, getters and setters
}

Il fournit à d'autres modules des types définis dans le packagecom.example.student.model. Ceci est réalisé en le définissant dans le fichiermodule-info.java:

module com.example.student.model {
    exports com.example.student.model;
}

Le modulecom.example.student.service fournit une interfacecom.example.student.service.StudentService avec des opérations CRUD abstraites:

public interface StudentService {
    public String create(Student student);
    public Student read(String registrationId);
    public Student update(Student student);
    public String delete(String registrationId);
}

Cela dépend du modulecom.example.student.model et rend les types définis dans le packagecom.example.student.service disponibles pour les autres modules:

module com.example.student.service {
    requires transitive com.example.student.model;
    exports com.example.student.service;
}

Nous fournissons un autre modulecom.example.student.service.dbimpl, qui fournit l'implémentationcom.example.student.service.dbimpl.StudentDbService pour le module ci-dessus:

public class StudentDbService implements StudentService {

    public String create(Student student) {
        // Creating student in DB
        return student.getRegistrationId();
    }

    public Student read(String registrationId) {
        // Reading student from DB
        return new Student();
    }

    public Student update(Student student) {
        // Updating sutdent in DB
        return student;
    }

    public String delete(String registrationId) {
        // Deleting sutdent in DB
        return registrationId;
    }
}

Il dépend directement decom.example.student.service et transitivement decom.example.student.model et sa définition sera:

module com.example.student.service.dbimpl {
    requires transitive com.example.student.service;
    requires java.logging;
    exports com.example.student.service.dbimpl;
}

Le module final est un module client - qui exploite le module d'implémentation de servicecom.example.student.service.dbimpl pour effectuer ses opérations:

public class StudentClient {

    public static void main(String[] args) {
        StudentService service = new StudentDbService();
        service.create(new Student());
        service.read("17SS0001");
        service.update(new Student());
        service.delete("17SS0001");
    }
}

Et sa définition est:

module com.example.student.client {
    requires com.example.student.service.dbimpl;
}

7. Compiler et exécuter l'échantillon

Nous avons fourni des scripts pour la compilation et l'exécution des modules ci-dessus pour les plates-formes Windows et Unix. Ceux-ci se trouvent sous le projetcore-java-9here. L'ordre d'exécution pour la plate-forme Windows est le suivant:

  1. compiler-student-model

  2. compiler-student-service

  3. compile-student-service-dbimpl

  4. compiler-étudiant-client

  5. run-student-client

L'ordre d'exécution pour la plate-forme Linux est assez simple:

  1. modules de compilation

  2. run-student-client

Dans les scripts ci-dessus, les deux arguments de ligne de commande suivants vous seront présentés:

  • –Module-source-path

  • –Module-path

Java 9 supprime le concept de classpath et introduit à la place le chemin de module. Ce chemin est l'emplacement où les modules peuvent être découverts.

Nous pouvons définir cela en utilisant l'argument de ligne de commande:–module-path.

Pour compiler plusieurs modules à la fois, nous utilisons les–module-source-path. Cet argument est utilisé pour fournir l'emplacement du code source du module.

8. Système de module appliqué à la source JDK

Chaque installation JDK est fournie avec unsrc.zip. Cette archive contient la base de code pour les API Java JDK. Si vous extrayez l'archive, vous trouverez plusieurs dossiers, peu commençant parjava, peu avecjavafx et le reste avecjdk.. Chaque dossier représente un module.

image

Les modules commençant parjava sont les modules JDK, ceux commençant parjavafx sont les modules JavaFX et les autres commençant parjdk sont les modules d'outils JDK.

Tous les modules JDK et tous les modules définis par l'utilisateur dépendent implicitement du modulejava.base. Le modulejava.base contient des API JDK couramment utilisées telles que Utils, Collections, IO, Concurrency, entre autres. Le graphe de dépendance des modules JDK est:

image

Vous pouvez également consulter les définitions des modules JDK pour avoir une idée de la syntaxe pour les définir dans lesmodule-info.java.

9. Conclusion

Dans cet article, nous avons étudié la création, la compilation et l'exécution d'une application modulaire simple. Nous avons également vu comment le code source de JDK avait été modularisé.

Il existe peu d'autres fonctionnalités intéressantes, telles que la création d'une durée d'exécution plus petite à l'aide de l'outil de création de liens, jlink, et la création de fichiers jar modulaires, entre autres fonctionnalités. Nous vous présenterons ces fonctionnalités en détail dans les prochains articles.

Le projet Jigsaw représente un énorme changement et nous devrons attendre de voir comment il sera accepté par l'écosystème de développeurs, en particulier par les créateurs d'outils et de bibliothèques.

Le code utilisé dans cet article se trouveover on GitHub.