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 modules dans le langage Java
  • et son implémentation en source JDK et en runtime Java

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

2. Modularité

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

  • couplage lâche entre les composants
  • des contrats clairs et des dépendances entre les composants
  • mise en œuvre 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, en particulier avec OSGi, les JAR étaient considérés comme l'unité de modularité.

Les fichiers JAR ont aidé à regrouper les composants associés, mais ils présentent certaines limites:

  • contrats explicites et dépendances entre JAR
  • faible encapsulation des éléments dans les JAR

2.2. JAR Enfer

Il y avait un autre problème avec les JAR: l'enfer des JAR. Plusieurs versions des JAR se trouvant sur le chemin de classe ont entraîné le chargement par le ClassLoader 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 l' exception ClassNotFoundException , 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, en utilisant JAR comme unité de modularité, les créateurs du langage Java ont proposé une nouvelle construction dans le langage appelé 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:

  • créer un système de modules pour le langage - implémenté sous JEP 261
  • l'appliquer à la source JDK - implémenté sous JEP 201
  • modulariser les bibliothèques JDK - implémentées sous JEP 200
  • mettre à jour le runtime pour prendre en charge la modularité - implémenté sous JEP 220
  • être capable de créer un environnement d'exécution plus petit avec un sous-ensemble de modules de JDK - implémenté sous JEP 282

Une autre initiative importante consiste à encapsuler les API internes dans le JDK, ceux qui sont sous le soleil des packages . * Et d'autres API non standard. Ces API n'ont jamais été destinées à être utilisées par le public et n'ont jamais été planifiées pour être maintenues. Mais la puissance de ces API a amené les développeurs Java à les exploiter dans le développement de différentes bibliothèques, frameworks et outils. Des remplacements ont été fournis pour quelques API internes et les autres ont été déplacés vers 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 l'empaquetage des 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 système du module

Le système de modules, implémenté dans le langage, les prend en charge comme une construction de niveau supérieur, tout comme les packages. Les développeurs peuvent organiser leur code en modules et déclarer les 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 packages qu'il met à disposition du public
  • les modules dont il dépend
  • tous les services qu'il consomme
  • toute implémentation pour le service qu'il fournit

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 utilisés via l' interface java.util.ServiceLoader .

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

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

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

5.1. Terminologies de définition de module

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

  • module : le fichier de définition du module commence par ce mot-clé suivi de son nom et de sa définition
  • requiert : est utilisé pour 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 lemot clé requires ; cela signifie que tout module qui dépend du module définissant nécessite transitive obtient une dépendance implicite sur le < 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: is used to indicate the packages that are accessible only at runtime and also available for introspection via Reflection APIs; this is quite significant to libraries like Spring and Hibernate, highly rely on Reflection APIs; opens can also be used at the module level in which case the entire module is accessible at runtime
  • uses: is used to indicate the service interface that this module is using; a type name, i.e., complete class/interface name, has to specified after this keyword
  • provides … with ...: they are used to indicate that it provides implementations, identified after the with keyword, for the service interface identified after the provides keyword

6. Simple Modular Application

Let us create a simple modular application with modules and their dependencies as indicated in the diagram below:

The com.baeldung.student.model is the root module. It defines model class com.baeldung.student.model.Student, which contains the following properties:

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

It provides other modules with types defined in the com.baeldung.student.model package. This is achieved by defining it in the file module-info.java:

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

The com.baeldung.student.service module provides an interface com.baeldung.student.service.StudentService with abstract CRUD operations:

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

It depends on the com.baeldung.student.model module and makes the types defined in the package com.baeldung.student.service available for other modules:

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

We provide another module com.baeldung.student.service.dbimpl, which provides the implementation com.baeldung.student.service.dbimpl.StudentDbService for the above module:

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 student in DB return student; } public String delete(String registrationId) { // Deleting student in DB return registrationId; } }

It depends directly on com.baeldung.student.service and transitively on com.baeldung.student.model and its definition will be:

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

The final module is a client module – which leverages the service implementation module com.baeldung.student.service.dbimpl to perform its operations:

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"); } }

And its definition is:

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

7. Compiling and Running the Sample

We have provided scripts for compiling and running the above modules for the Windows and the Unix platforms. These can be found under the core-java-9 project here. The order of execution for Windows platform is:

  1. compile-student-model
  2. compile-student-service
  3. compile-student-service-dbimpl
  4. compile-student-client
  5. run-student-client

The order of execution for Linux platform is quite simple:

  1. compile-modules
  2. run-student-client

In the scripts above, you will be introduced to the following two command line arguments:

  • –module-source-path
  • –module-path

Java 9 is doing away with the concept of classpath and instead introduces module path. This path is the location where the modules can be discovered.

We can set this by using the command line argument: –module-path.

To compile multiple modules at once, we make use of the –module-source-path. This argument is used to provide the location for the module source code.

8. Module System Applied to JDK Source

Every JDK installation is supplied with a src.zip. This archive contains the code base for the JDK Java APIs. If you extract the archive, you will find multiple folders, few starting with java, few with javafx and the rest with jdk. Each folder represents a module.

The modules starting with java are the JDK modules, those starting with javafx are the JavaFX modules and others starting with jdk are the JDK tools modules.

All JDK modules and all the user defined modules implicitly depend on the java.base module. The java.base module contains commonly used JDK APIs like Utils, Collections, IO, Concurrency among others. The dependency graph of the JDK modules is:

You can also look at the definitions of the JDK modules to get an idea of the syntax for defining them in the module-info.java.

9. Conclusion

In this article, we looked at creating, compiling and running a simple modular application. We also saw how the JDK source code had been modularized.

There are few more exciting features, like creating smaller runtime using the linker tool – jlink and creating modular jars among other features. We will introduce you to those features in details in future articles.

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

Le code utilisé dans cet article se trouve à l'adresse over sur GitHub.