Modularidad en Java 9

La principal innovación en Java 9 fue la introducción de la modularidad. Se habló mucho sobre esta característica, la fecha de lanzamiento se pospuso varias veces para terminar todo correctamente. En esta publicación, hablaremos sobre lo que proporciona el mecanismo de los módulos y lo útil que trajo Java 9 en general. La base para la publicación fue el informe de mi colega, Sergei Malkevich .




Para implementar los módulos en esta versión de Java, se asignó un proyecto completo, Project Jigsaw, que incluye varios JEP y JSR.



Para los fanáticos de la documentación oficial, puede leer más sobre cada JEP aquí .

Más información sobre Project Jigsaw


El Proyecto Jigsaw, que implementa la modularidad, comenzó a desarrollarse en 2005: se lanzó el primer JSR 277, y ya en 2008, comenzó el trabajo directo en el proyecto. El lanzamiento tuvo lugar solo en 2017. Es decir, para atornillar los módulos en Java, tomó casi 10 años. Lo que, de hecho, enfatiza la escala completa del trabajo y los cambios que se hicieron durante la implementación de la modularidad.

¿Cuáles son los objetivos establecidos por los desarrolladores:

  • facilitar el desarrollo de grandes aplicaciones y bibliotecas;
  • Mejore la seguridad de Java SE en general, y JDK en particular;
  • aumentar el rendimiento de la aplicación;
  • cree la capacidad de reducir el tamaño del JRE para ejecutarse en dispositivos pequeños para no consumir demasiada memoria;
  • JAR HELL (más sobre eso más adelante).

Lo útil que trajo Java 9


Antes de la versión 9, JDK y JRE eran monolíticos. Su tamaño creció con cada lanzamiento. Java 8 ya ocupaba cientos de megabytes, y todo esto los desarrolladores tuvieron que "llevarlos" cada vez para poder ejecutar aplicaciones Java. Solo rt.jar solo pesa alrededor de 60 Mb. Bueno, aquí también agregamos un inicio lento y un alto consumo de memoria. Java 9 vino al rescate.

En JDK 9Se introdujo la separación de módulos, a saber, el JDK se dividió en 73 módulos. Y con cada nueva versión, el número de estos módulos está creciendo. En la versión 11, este número es cercano a 100. Esta separación permitió a los desarrolladores crear la utilidad JLINK. Con JLINK, puede crear conjuntos JRE personalizados que incluirán solo los módulos "necesarios" que su aplicación realmente necesita. Por lo tanto, una aplicación simple y algunos customJRE con un conjunto mínimo (o pequeño) de módulos pueden caber en 20 Mb, lo cual es una buena noticia.

La lista de módulos se puede encontrar aquí .

Con la llegada de Java 9, la estructura JDK ha cambiado: ahora es idéntica a la estructura JRE. Si antes el JDK incluía la carpeta JRE, donde bin está nuevamente y los archivos están duplicados, ahora todo se ve así:



Módulos


Realmente. ¿Qué es un módulo? Un módulo es un nuevo nivel de agregación de paquetes y recursos (orig. "Un grupo único de paquetes relacionados, reutilizables, así como recursos y un descriptor de módulo" ).

Los módulos se entregan en archivos JAR con paquetes y descriptores de módulos
module-info.java . El archivo module-info.java contiene una descripción del módulo:
nombre, dependencias, paquetes exportados, servicios consumidos y proporcionados, permisos para el acceso de reflexión.

Ejemplos de descripciones de descriptores de módulos:

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

Después del módulo de palabras clave, tenemos el nombre del paquete jdk.javadoc , que depende de otro paquete java.xml y depende transitivamente de otros paquetes.

Echemos un vistazo más de cerca a cada una de las palabras clave:

  • requiere indica los módulos de los que depende el módulo actual;

  • requiere transitivo , una dependencia transitiva, significa lo siguiente: si el módulo m1 depende transitivamente del módulo m2 , y tenemos algún tercer módulo mX , que depende de m1 , el módulo mX también tendrá acceso a m2 ;

  • requiere estática le permite especificar dependencias en tiempo de compilación;

  • 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 poco sobre los servicios.

Digamos que tenemos varios módulos conectados que implementan un servicio abstracto: MyService . Al compilar la aplicación, tenemos la oportunidad de decidir qué implementación de servicio usar al "arrastrar" los módulos de implementación de servicio necesarios a --module-path :

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

Por lo tanto, el iterador devuelto contiene una lista de implementaciones de la interfaz MyService. De hecho, contendrá todas las implementaciones encontradas en los módulos que se encuentran en --module-path .

¿Por qué, en principio, se introdujeron los servicios? Son necesarios para mostrar cómo se usará nuestro código. Es decir, hay un papel semántico. Además, la modularidad se trata de encapsulación y seguridad, ya que podemos hacer que la implementación sea privada y excluir la posibilidad de acceso no autorizado a través de la reflexión.

Además, una de las opciones para usar los servicios es una implementación bastante simple de complementos. Podemos implementar la interfaz del complemento para nuestra aplicación y conectar módulos para trabajar con ellos.

Volvamos a la sintaxis para describir módulos:

Hasta 9ki, a través de la reflexión, teníamos acceso a casi todo y podíamos hacer lo que queramos y con lo que queramos. Y la novena versión, como ya se mencionó, le permite protegerse del acceso de reflexión "ilegal".

Podemos abrir completamente el módulo para el acceso de reflexión declarando abierto :

open module my.module {
}

O bien, podemos especificar cualquier paquete para el acceso de reflexión declarando aperturas :

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

Aquí también es posible usar abre com.my.coolpackage a ... , dando así acceso a la reflexión com.my.coolpackage paquete del paquete que indicaremos después a .

Tipos de módulos


Project Jigsaw clasifica los módulos de la siguiente manera:

  • 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


Con la llegada de los módulos, apareció un nuevo concepto: module-path . En esencia, esta es la misma ruta de clase , pero para los módulos.

El lanzamiento de una aplicación modular es el siguiente:



en el modo de lanzamiento normal, especificamos las opciones y la ruta completa a la clase principal. En caso de que queramos trabajar con módulos, también especificamos opciones y el parámetro -m o -module , que solo indica que ejecutaremos los módulos. Es decir, traducimos automáticamente nuestra aplicación al modo modular. A continuación, indicamos el nombre del módulo y la ruta a la clase principal desde el módulo.

Además, si en modo normal estamos acostumbrados a trabajar con las opciones -cp y --class-path, en modo modular, prescribimos un nuevo parámetro -p y --module-path , que indica las rutas a los módulos utilizados en la aplicación.

A menudo me encuentro con el hecho de que los desarrolladores no cambian a la versión 9+, porque creen que tendrán que trabajar con módulos. Aunque, de hecho, podemos ejecutar nuestras aplicaciones en el modo antiguo, simplemente sin escribir un parámetro o usar módulos, sino usando solo otros chips nuevos.

Jar el infierno


También quiero detenerme en diagonal en el problema de Jar Hell.



¿Qué es Jar Hell en pocas palabras? Por ejemplo, tenemos una especie de nuestra aplicación y que depende de la X biblioteca y la biblioteca Y . Al mismo tiempo, ambas bibliotecas dependen de la biblioteca Z , pero de diferentes versiones: X depende de la versión 1 , Y de la versión 2 . Bueno, si la versión 2 es retrocompatible con la versión 1, entonces no hay problema. Y si no, es obvio que estamos teniendo un conflicto de versión, es decir, la misma biblioteca no puede cargarse en la memoria por el mismo cargador de clases.

¿Cómo salir de esta situación? Hay métodos estándar que los desarrolladores han estado utilizando desde el primer Java, por ejemplo, excluir , alguien usa complementos para Maven, que cambian el nombre de los paquetes raíz de la biblioteca. O bien, los desarrolladores están buscando diferentes versiones de la biblioteca X para encontrar una opción compatible.

¿Por qué? Yo: los primeros prototipos de Jigsaw implicaban que el módulo tenía una versión y permitía la carga de varias versiones a través de diferentes ClassLoaders, pero luego fue abandonado. Como resultado, la "bala de plata", que muchos esperaban, no funcionó.

Pero, desde el primer momento, estábamos un poco a salvo de tales problemas. Java 9 deshabilita los paquetes divididos- paquetes que se dividen en varios módulos. Es decir, si tenemos el paquete com.my.coolpackage en un módulo, no podemos usarlo en otro módulo dentro de la misma aplicación. Cuando inicia la aplicación con módulos que contienen los mismos paquetes, simplemente nos bloqueamos. Esta pequeña mejora elimina la posibilidad de un comportamiento impredecible en relación con la descarga de paquetes Split.

Además, además de los módulos en sí, también hay un mecanismo de capa o capas de rompecabezas , que también ayuda a hacer frente al problema de Jar Hell.

Una capa de rompecabezas se puede definir como un sistema modular local. Y aquí vale la pena señalar que los paquetes Split mencionados anteriormente están prohibidos solo dentro del marco de una capa de Jigsaw. Los módulos con los mismos paquetes tienen un lugar para estar, pero deben pertenecer a diferentes capas.

Se ve así:



cuando se inicia la aplicación, se crea una capa de arranque , que incluye módulos de plataforma cargados por Bootstrap, módulos de plataforma adicionales cargados por el cargador de plataformas y nuestros módulos de aplicaciones cargados por el cargador de aplicaciones.

En cualquier momento, podemos crear nuestras propias capas y "poner" módulos de diferentes versiones allí y no caer.

Hay una gran y detallada charla de YouTube sobre el tema: Escaping Jar Hell con Jigsaw Layers

Conclusión


El motor de módulos de Java 9 nos abre nuevas posibilidades, mientras que el soporte para bibliotecas de hoy es bastante pequeño. Sí, la gente ejecuta Spring, Spring Boot, etc. Pero la mayoría de las bibliotecas no han cambiado al uso completo de los módulos. Aparentemente, por lo tanto, todos estos cambios fueron percibidos escépticamente por la comunidad técnica. Los módulos nos brindan nuevas oportunidades, pero la cuestión de la demanda permanece abierta.

Y finalmente, ofrezco una selección de materiales sobre este tema: Resumen del módulo JDsaw de

Project Jigsaw Paul Deitel - Comprensión de los módulos de Java 9 baeldung.com - Introducción al proyecto Jigsaw Alex Buckley - Desarrollo modular con JDK 9 Evgeny Kozlov - Módulos en Java










All Articles