Introducción a Project Jigsaw

1. Introducción

Project Jigsaw es un proyecto general con las nuevas características dirigidas a dos aspectos:

  • la introducción del sistema de módulos en el lenguaje Java
  • y su implementación en fuente JDK y tiempo de ejecución de Java

En este artículo, le presentaremos el proyecto Jigsaw y sus características y finalmente lo concluiremos con una aplicación modular simple.

2. Modularidad

En pocas palabras, la modularidad es un principio de diseño que nos ayuda a lograr:

  • acoplamiento flojo entre componentes
  • contratos claros y dependencias entre componentes
  • implementación oculta mediante encapsulación fuerte

2.1. Unidad de modularidad

Ahora surge la pregunta de cuál es la unidad de modularidad. En el mundo de Java, especialmente con OSGi, los JAR se consideraban la unidad de modularidad.

Los JAR ayudaron a agrupar los componentes relacionados, pero tienen algunas limitaciones:

  • contratos explícitos y dependencias entre JAR
  • Encapsulación débil de elementos dentro de los JAR

2.2. JAR Infierno

Había otro problema con los JAR: el infierno de los JAR. Varias versiones de los JAR que se encuentran en la ruta de clases dieron como resultado que ClassLoader cargara la primera clase encontrada del JAR, con resultados muy inesperados.

El otro problema con la JVM usando classpath fue que la compilación de la aplicación sería exitosa, pero la aplicación fallará en tiempo de ejecución con ClassNotFoundException , debido a que faltan JARs en la ruta de clase en tiempo de ejecución.

2.3. Nueva unidad de modularidad

Con todas estas limitaciones, al usar JAR como unidad de modularidad, los creadores del lenguaje Java idearon una nueva construcción en el lenguaje llamada módulos. Y con esto, hay un sistema modular completamente nuevo planeado para Java.

3. Proyecto Jigsaw

Las principales motivaciones de este proyecto son:

  • crear un sistema de módulos para el idioma - implementado bajo JEP 261
  • aplicarlo a la fuente JDK - implementado bajo JEP 201
  • modularizar las bibliotecas JDK - implementado bajo JEP 200
  • actualizar el tiempo de ejecución para admitir la modularidad , implementado bajo JEP 220
  • ser capaz de crear un tiempo de ejecución más pequeño con un subconjunto de módulos de JDK - implementado bajo JEP 282

Otra iniciativa importante es encapsular las API internas en el JDK, aquellas que están bajo el sol. * Paquetes y otras API no estándar. Estas API nunca fueron diseñadas para ser utilizadas por el público y nunca se planeó su mantenimiento. Pero el poder de estas API hizo que los desarrolladores de Java las aprovecharan en el desarrollo de diferentes bibliotecas, marcos y herramientas. Se han proporcionado reemplazos para algunas API internas y las otras se han trasladado a módulos internos.

4. Nuevas herramientas para la modularidad

  • jdeps : ayuda a analizar la base del código para identificar las dependencias en las API de JDK y los JAR de terceros. También menciona el nombre del módulo donde se puede encontrar la API de JDK. Esto facilita la modularización de la base del código.
  • jdeprscan : ayuda a analizar el código base para el uso de las API obsoletas
  • jlink : ayuda a crear un tiempo de ejecución más pequeño al combinar los módulos de la aplicación y del JDK
  • jmod : ayuda a trabajar con archivos jmod. jmod es un nuevo formato para empaquetar los módulos. Este formato permite incluir código nativo, archivos de configuración y otros datos que no caben en archivos JAR

5. Arquitectura del sistema de módulos

El sistema de módulos, implementado en el lenguaje, los admite como una construcción de nivel superior, al igual que los paquetes. Los desarrolladores pueden organizar su código en módulos y declarar dependencias entre ellos en sus respectivos archivos de definición de módulo.

Un archivo de definición de módulo, denominado module-info.java , contiene:

  • su nombre
  • los paquetes que pone a disposición del público
  • los módulos de los que depende
  • cualquier servicio que consume
  • cualquier implementación para el servicio que brinda

Los dos últimos elementos de la lista anterior no se utilizan con frecuencia. Se utilizan solo cuando los servicios se proporcionan y consumen a través de la interfaz java.util.ServiceLoader .

Una estructura general del módulo se ve así:

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

La ilustración anterior define dos módulos: com.baeldung.reader y com.baeldung.writer . Cada uno de ellos tiene su definición especificada en module-info.java y los archivos de código ubicados en com / baeldung / reader y com / baeldung / writer , respectivamente.

5.1. Terminologías de definición de módulos

Echemos un vistazo a algunas de las terminologías; que usaremos al definir el módulo (es decir, dentro de module-info.java) :

  • módulo : el archivo de definición del módulo comienza con esta palabra clave seguida de su nombre y definición
  • requiere : se utiliza para indicar los módulos de los que depende; se debe especificar un nombre de módulo después de esta palabra clave
  • transitivo : se especifica después de lapalabra clave require ; esto significa que cualquier módulo que dependa de la definición del módulo requiere transitivo obtiene una dependencia implícita del <nombre del módulo>
  • exportaciones : se utiliza para indicar los paquetes dentro del módulo disponibles públicamente; se debe especificar un nombre de paquete después de esta palabra clave
  • 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 es un gran cambio, y tendremos que esperar y ver cómo es aceptado por el ecosistema de desarrolladores, en particular con las herramientas y los creadores de bibliotecas.

El código utilizado en este artículo se puede encontrar en GitHub.