En esta segunda parte exploramos el JDK modular, la resolución de módulos, carga de recursos, y cómo migrar una aplicación a módulos. Si no leíste la primera parte, te recomiendo que lo hagas ahora.
Contenido
- 1 El JDK Modular
- 2 Herramientas
- 3 Resolución de módulos
- 4 El API de Módulos
- 5 Migración
- 6 WTF java.logging
- 7 Conclusión
1. El JDK Modular
En el año 2000 el JDK tenía múltiples dependencias entre paquetes no relacionados:
Esto ocurre porque en un proyecto en el que no haya límites claros –como una división en librerías– es fácil tomar atajos que creen dependencias sin que nadie lo note. Incluso en un proyecto con desarrolladores disciplinados como el JDK, hicieron falta años para refactorizar hacia un sistema modular.
En 2017, el JDK modularizado tiene este aspecto (versión simplificada):
El JDK 9 tiene 94 módulos. Puedes listarlos ejecutando:
1 2 3 4 5 6 7 |
$ java --list-modules java.activation@9 java.base@9 java.compiler@9 java.corba@9 ... |
Verás que los módulos tienen los siguientes prefijos:
|
Módulos del núcleo de la plataforma Java. Estos son los módulos del lenguaje, módulos enterprise, o módulos agregadores que agrupan los dos anteriores. |
|
Módulos de Java FX. |
|
APIs y herramientas del JDK como el compilador, javadoc, y consola. No son parte de la especificación del lenguaje. |
|
Módulos experimentales sujetos a cambios. Por ejemplo, |
|
Tipos no soportados y que pueden ser eliminadas en cualquier momento. Por ejemplo, |
|
Módulos específicos de la implementación de Oracle del JDK. |
Los módulos agregadores son módulos sin código propio que agrupan a otros módulos mediante sentencias requires transitive
. En el JDK hay dos:
- java.se: Java Standard Edition
- java.se.ee: Java Enterprise Edition
Un proyecto bien diseñado no debería requerir los módulos agregadores, a menos que necesite la plataforma entera, lo cual es improbable.
1.1. La estructura del JDK
El JDK previo a Java 9 tenía este aspecto:
1 2 3 4 5 6 7 8 9 |
├──bin │ └── java │ └── javac ├──jre │ └── bin │ │ └── java │ └── lib │ └── rt.jar └──lib |
Pero ahora tiene este otro:
1 2 3 4 5 6 7 8 9 10 |
├──bin │ └── java │ └── javac ├──conf ├──include ├──jmods │ └── java.base.jmod │ └── … ├──legal └──lib |
Los cambios en la estructura de directorios están documentados en
JEP 220: Modular Run-Time Images.
2. Herramientas
El JDK tiene varias herramientas para operar con módulos:
|
Muestra las dependencias de código con el JDK y módulos de terceros. |
|
Muestra código obsoleto (deprecated). |
|
Crea imágenes que contienen la aplicación y un runtime de tamaño mínimo para ejecutarla. |
|
Trabaja con ficheros jmod. Este formato es un JAR con código nativo, ficheros de configuración, y otros datos. |
Para describir un módulo, por ejemplo java.logging:
1 2 3 4 5 6 7 8 9 |
$ java --describe-module java.logging java.logging@9 exports java.util.logging requires java.base mandated provides jdk.internal.logger.DefaultLoggerFinder with sun.util.logging.internal.LoggingProviderImpl contains sun.net.www.protocol.http.logging contains sun.util.logging.internal contains sun.util.logging.resources |
Esto muestra los paquetes que contiene, exporta, servicios provistos, usados, y paquetes requeridos.
Para listar que paquetes dependen de otro, por ejemplo de java.logging, hace falta un poco de bash:
1 |
for file in $(/usr/libexec/java_home -v 9)/jmods/*; do mod=`basename $file .jmod`; java --describe-module $mod | grep "requires java.logging" | ifne echo $mod | sort; done |
Ojo Mac users, el script de arriba solo funciona si tienes un bash moderno. MacOS tiene un bash de hace diez años por temas de licencias. Escribir scripts compatibles con POSIX requiere más trabajo, así que el mío lo actualizo con homebrew. Hay otros scripts en este tutorial que requieren esta actualización.
1234567 brew install bashchsh -s /usr/local/bin/bashbrew install coreutils moreutils# esto no tiene que ver, pero son otras cosas útiles que suelo instalarbrew install gnu-sed --with-default-namesbrew install git openssl htop pstree
2.1. jlink
jlink construye una imagen de runtime con solo lo necesario para ejecutar la aplicación. La imagen es nativa del sistema operativo donde la creas.
¿Para qué sirve crear una imagen?
- Menor consumo de disco y memoria. Esto es especialmente útil al usar microservicios e instalar en dispositivos móviles.
- Aplicaciones autocontenidas.
- Optimizaciones en tiempo de enlazado. Eliminación de código inalcanzable, compresión de la imagen, constant folding,
inlining de funciones, etc. - Mayor seguridad, porque a menor código, menor probabilidad de que nos afecten los defectos de una clase concreta.
jlink está estructurado en plugins. Puedes ejecutar
jlink --list-plugins
para verlos todos. Estos son dos plugins interesantes que están desactivados por defecto:
|
Compresión de la imagen. Puedes no comprimir (0), crear una tabla global de cadenas (1), o comprimir en zip (2). |
|
Para añadir localización española pasa |
Para crear una imagen
1 2 3 4 |
jlink --module-path Algorithms/target/classes:Hello/target/classes:$JAVA_HOME/jmods --add-modules Algorithms,Hello --launcher start=Hello/com.example.app.Hello --output Hello-image |
Este comando está en el script createHelloImagen.sh
del código de ejemplo que acompaña a este artículo. La estructura de la imagen resultante es esta:
1 2 3 4 5 6 |
image ├── bin ├── conf ├── include ├── legal └── lib |
En caso de que el módulo realice reflexión de otro y no lo declare, no se incluirá en la imagen. Aun así, puedes ejecutar la imagen y añadir el módulo con el flag --add-modules
. jlink solo funciona si tu proyecto está formado por módulos explícitos (no módulos anónimos ni automáticos).
Para mostrar los proveedores de un servicio
1 2 3 4 5 6 |
$ jlink --add-modules Algorithms,Hello,HelloTest --suggest-providers algorithms.sort.Sortable --module-path Algorithms/target/classes:Hello/target/classes:HelloTest/target/classes:$JAVA_HOME/jmods Suggested providers: Algorithms provides algorithms.sort.Sortable used by Hello |
Pruebalo ejecutando ./showProviders.sh
en el código de ejemplo.
Para añadir por adelantado todas las implementaciones de un servicio en los módulos observables, añade el flag --bind-services
al comando de creación de imagen. El efecto será que todos los módulos que contengan una implementación de un servicio serán añadidos a la imagen.
1 2 3 4 5 |
jlink --module-path Algorithms/target/classes:Hello/target/classes:$JAVA_HOME/jmods --add-modules Algorithms,Hello --launcher start=Hello/com.example.app.Hello --output Hello-image --bind-services |
Para mostrar todos los módulos de una imagen ejecuta
1 |
imagen/bin/java --list-modules |
Al hacer esto para la Hello-image que había creado, me encontré 23 módulos del jdk y 17 java. No entiendo porqué empaqueta módulos como java.smartcardio
. En total sale una imagen de 146Mb en disco, frente a los 517Mb del JDK. Si además la comprimimos la imagen se queda en 75Mb.
Para comprimir la imagen añade los flags
--strip-debug
and
--compress=n
, donde n puede ser 0, 1, o 2.
Para escoger la máquina virtual Hotspot usa --vm={client|server|minimal|all}
. Las opciones minimal y client no funcionan en MacOS. Al comprimir la imagen solo cambia un fichero: imagen/lib/modules
, que pasa de 114Mb a 41Mb.
Para añadir un idioma que no sea el inglés, añade el flag
--add-module jdk.localedata
. Este módulo contiene las traducciones para todos los idiomas soportados. El inglés ya está incluido en
jdk.localedata
.
Para crear una imagen para otro sistema operativo tienes que bajar el JDK de esa plataforma y apuntar al directorio que contiene sus jmods. ¿Porqué no valen los jmods de tu JDK? porque los jmods, además de código Java multiplataforma, también contienen código nativo. Suponiendo que hayamos descargado el JDK 9 de Windows, ejecutamos esto:
1 |
$ jlink --module-path jdk-9_windows-x64/jmods |
Un problema adicional es extraer los jmods de un JDK nativo. Si tienes MacOS, y quieres crear una imagen para Windows, Oracle te da un JDK en formato .exe (ejecutable de Windows). Necesitarás un sistema operativo Windows para poder descomprimirlo.
2.2. jdeps
jdeps realiza un análisis estático del bytecode de la aplicación y muestra los módulos JDK imprescindibles para ejecutar la clase o JAR que estemos analizando. Como con jlink, cuidado con usar reflexión, porque jdeps no la detecta.
Para mostrar las dependencias de una clase:
1 2 3 4 5 6 |
$ jdeps -verbose:class --module-path Algorithms/target/classes Algorithms/target/classes/Algorithms/algorithms/sort/Sortable.class Sortable.class -> java.base algorithms.sort.Sortable -> java.lang.Comparable java.base algorithms.sort.Sortable -> java.lang.Object java.base |
Para mostrar las dependencias de un JAR:
1 2 3 4 5 |
$ jdeps -summary --module-path Algorithms/target/classes:$JAVA_HOME/jmods ./Hello/target/Hello-1.0-SNAPSHOT.jar Hello -> Algorithms Hello -> java.base Hello -> java.logging |
Si además quieres mostrar las dependencias transitivas en tiempo de ejecución añade el flag -recursive
.
Para buscar usos de código privado en nuestro código
1 2 |
jdeps -jdkinternals --module-path Algorithms/target/classes jdeps -jdkinternals -cp Algorithms/target/classes/Algorithms |
En JDK 9, el código que use código privado del JDK (por ejemplo, sun.*) producirá errores. Podemos exportar este código privado mediante el flag --add-exports
, pero la solución correcta es usar las
clases del JDK que reemplazan a sun.*.
Para mostrar un gráfico de dependencias
1 2 3 4 5 |
cd SimpleHello jdeps --dotoutput . --module-path Algorithms/target/classes:$JAVA_HOME/jmods Algorithms brew install graphviz dot Algorithms.dot -Tpng -o Algorithms.png open Algorithms.png |
SimpleHello es el ejemplillo de la parte 1 del tutorial. Idea muestra un gráfico mucho mejor que este, pero si quieres
generar un PNG para un javadoc por ejemplo, con graphviz queda algo así:
3. Resolución de módulos
Resolver un módulo es calcular el número mínimo de módulos a partir de un grafo de dependencias y un módulo raíz de ese grafo. Esto lo realiza el compilador y el entorno de ejecución. Ojo, los módulos implicados en este cálculo se leen del module path, no del classpath.
Llamamos módulos observables a aquellos a los que la plataforma tiene acceso para satisfacer las dependencias entre módulos. Están formados por los módulos de la plataforma y los módulos del directorio que pasemos por línea de comando.
Para ver los módulos observables en un directorio dado
1 |
java --module-path directory --list-modules |
Podemos pasar varios directorios separados por el carácter de separación de directorios del sistema operativo. En MacOS, el carácter es :
(dos puntos), en Windows es ;
(punto y coma).
Para resolver los módulos el compilador o entorno de ejecución
- Empieza en el módulo que contiene el punto de entrada a la aplicación.
- Lee la lista de módulos requeridos por el módulo raíz y hace recursión por las dependencias de esos módulos.
La resolución de errores terminará con error si
- Falta un módulo requerido.
- Hay dependencias cíclicas entre módulos.
- Hay dos módulos con el mismo nombre.
- Hay un paquete partido (split package). Los
paquetes partidos son paquetes cuyo código existe en dos módulos observables. Esto es un error de compilación que podría evitarse en tiempo de ejecución usando un cargador de clases adicional, pero la solución correcta es simplemente poner el paquete en un único módulo.
Para ver la resolución de módulos lanza la aplicación con el flag
--show-module-resolution
:
1 |
java --show-module-resolution --module-path out -m packt.addressbook/packt.addressbook.Main |
3.1. El módulo anónimo
El módulo anónimo (unnamed module) es aquel módulo donde se añade el código para el que no hay definido explícitamente un módulo. El módulo anónimo tiene las características siguientes:
- El código en el módulo anónimo puede leer todo el código del classpath sin restricciones. Sin embargo, no puede acceder el código no exportado de los módulos observables.
- Durante la compilación, el módulo anónimo usa al módulo
java.se
como módulo raíz (por tanto, el código de java.se.ee no es legible por defecto). - El módulo anónimo sólo es legible desde otros módulos anónimos.
- Si hay un paquete definido en un módulo anónimo y otro no anónimo, el paquete del módulo anónimo es ignorado.
Cada cargador de clases tiene un módulo anónimo, devuelto por el método ClassLoader::getUnnamedModule
.
3.2. Módulos automáticos
Un
módulo automático es un JAR normal cargado desde el modulepath. Se comporta como un módulo porque el sistema
de módulos crea un descriptor automático. Los módulos automáticos tienen las características siguientes:
- Tiene acceso a todos los módulos, incluyendo al módulo anónimo.
- Requiere de forma transitiva todos los módulos en el modulepath.
- Todos sus paquetes son legibles (están exportados por defecto).
- Su nombre de módulo se decide a partir del nombre de fichero, pero también podemos escribirlo nosotros en el fichero
META-INF/MANIFEST.MF. - No puede tener un paquete que también exista en otro módulo.
- Requiere que las dependencias del propio jar estén presentes. Podemos usar
jdeps -recursive -summary somefile.jar
para mostrar sus dependencias a otros módulos, y
jdeps --generate-module-info . somefile.jar
para generar un descriptor automático de un módulo.
Con este ya hemos visto los tres tipos de módulos que existen:
Tipo |
¿Qué es? |
¿Qué lee? |
---|---|---|
anónimo |
código en el classpath |
módulos explícitos y automáticos |
explícito |
un JAR + descriptor de módulo |
módulos explícitos y automáticos |
automático |
JAR en el modulepath |
lee transitivamente |
1
La transitividad solo aplica a los módulos explícitos porque son los únicos que declaran dependencias.
3.2.1. Cómo nombrar un módulo
- Por defecto es el nombre del fichero sin el número de versión ni la extensión jar. Cualquier guión en el nombre se convierte
en un punto. - También podemos darle valor añadiendo una entrada al manifiesto del JAR. Por ejemplo:
Automatic-Module-Name: Paquito
.
Para nombrar un módulo automático con el manifiesto
1 2 3 4 |
$ jar xvf antlr.jar META-INF/MANIFEST.MF inflated: META-INF/MANIFEST.MF $ echo Automatic-Module-Name: Paquito >> META-INF/MANIFEST.MF $ jar umf META-INF/MANIFEST.MF antlr.jar |
Para nombrar un módulo automático con Maven
1 2 3 4 5 6 7 8 9 10 11 |
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestEntries> <Automatic-Module-Name>Paquito</Automatic-Module-Name> </manifestEntries> </archive> </configuration> </plugin> |
Para nombrar un módulo automático con un descriptor
Esto lo podemos hacer a mano o automáticamente con jdeps:
1 |
jdeps --generate-module-info . commons-logging-api-1.1.jar |
Cuando falta código nativo
He puesto un ejemplo con commons-logging porque con antlr no me funcionó. Me salía un error que decía ActionLexer not found, que es una clase que está ahí, pero luego he visto que necesita código nativo.
1234 $ jdeps --generate-module-info . antlr.jarMissing dependence: ./Paquito/module-info.java not generatedError: missing dependenciesantlr.CSharpCodeGenerator -> antlr.actions.csharp.ActionLexer not found
3.3. Layering
Cuando el entorno de ejecución calcula el grafo de módulos accesibles, crea un layer, que mapea cada módulo en el grafo al cargador de clases responsable de cargar los tipos definidos en ese módulo.
Cuando la máquina virtual arranca, crea un “boot layer” que resuelve el módulo que contiene el punto de entrada a la aplicación
contra los módulos observables. Normalmente este será el único layer creado, pero en aplicaciones sofisticadas es posible
añadir layers adicionales que definan un conjunto diferente de módulos observables, o carguen más de una versión de un
módulo.
Las aplicaciones que usan layers adicionales pueden ser IDEs, entornos de ejecución de pruebas, servidores de aplicaciones,
o aplicaciones que extiendan su funcionalidad con plugins.
Si quieres saber más sobre el tema consulta
- The State of the Module System >
Layers - java.lang.ModuleLayer
4. El API de Módulos
El API de módulos permite operar con módulos: carga, lectura, modificación, construcción, búsqueda.
Su código está en estos paquetes:
- java.lang: Module, ModuleLayer, LayerInstantiationException
- java.lang.module
4.1. Carga de Recursos
1 2 3 4 5 6 7 8 |
// para cargar un recurso leyendolo del modulepath ClassLoader.getSystemResources(resourceName) // para cargarlo si conoces una clase del módulo donde está el recurso Class.forName(className).getResourceAsStream(resourceName) // para cargarlo si conoces el módulo que contiene el recurso ModuleLayer.boot().findModule(moduleName).getResourceAsStream(resourceName) |
Ten cuenta que cuando un fichero está en el directorio raíz o dentro del directorio META-INF
, el recurso no está encapsulado, y por tanto es accesible mediante escaneo del modulepath usando ClassLoader.
Para ver un ejemplo completo puedes consultar esta respuesta de SO, o clonar este repositorio de GitHub.
5. Migración
Tienes que dar dos pasos:
- Compila y ejecuta la aplicación en JDK 9 con classpath.
- Mueve tu aplicación a módulos y sustituye el classpath por el modulepath.
5.1. JDK 9 con classpath
Cuando compilas y ejecutas tu aplicación con JDK 9 usando el classpath, tu código y librerías pasan a estar en el
módulo anónimo. Todo módulo respecta las reglas de encapsulación de otros módulos, así que posiblemente encuentres
infracciones de encapsulación que tendrás que arreglar. A continuación los comento.
5.1.1. Posibles problemas
-
Reflexión de elementos privados en elementos del JDK. O sea, tú, o una librería que usas ha hecho un
setAccessible(true)
a un elemento privado. Para facilitar la migración, esto es un warning (equivalente a pasar--illegal-access=permit
en línea de comandos), pero en futuras releases lo cambiarán a
ser un error (--illegal-access=deny
). Por eso es buena idea lanzar la app con un
deny
para hacerte una idea del trabajo que podrías tener cuando lo cambien.
-
Uso de código interno de la plataforma. Por ejemplo, estás usando sun.*.
- Si el código interno continúa siendo accesible no requiere acción alguna por tu parte, pero deberías pensar en usar alguna alternativa listada en
Replace use of JDK’s internal APIs. Estas alternativas te las muestra jdeps si sacas un listado de accesos al código interno desde tu código. - Si el código interno es público pero no está exportado y quieres una solución temporal, puedes exportar el paquete
desde línea de comandos, tanto en java como en javac, usando
--add-exports módulo/paquete=móduloAlQueExportas
. Para exportar al módulo anónimo usa ALL-UNNAMED como nombre. - Si el código interno ha sido eliminado o no hay alternativa clara tendrás que buscarte la vida.
- Si el código interno continúa siendo accesible no requiere acción alguna por tu parte, pero deberías pensar en usar alguna alternativa listada en
-
Paquetes partidos. Este es el caso en el que tienes un mismo paquete en diferentes módulos. Muevelos al mismo
módulo o renombralos. -
Clases movidas a java.se.ee. Hay paquetes que ahora están en Java EE así que tienes que importar el módulo
correspondiente.
Para saber en que módulo está una clase
- Si estás en Idea puedes escribir el nombre de la clase en el código fuente y dejar que te sugiera un import. Por ejemplo,
escribiendo Datasource sugiere importarjavax.activation.DataSource
. El import aparecerá inicialmente en rojo si pertenece
a un módulo no requerido, y haciendo mouseover te sugerirá el nombre de ese módulo que falta. - Sin ayuda de un IDE, si sabes en qué paquete está puedes hacer por ejemplo:
java --list-modules | grep activation
. A mí esto alguna vez no me ha funcionado así que hice el siguiente script para buscar por nombre de clase. Desconozco
si hay un modo más sencillo.
1 2 3 4 5 6 |
$ findclass() { for file in $(/usr/libexec/java_home -v 9)/jmods/*; do jar -tf $file | grep $1 | ifne sh -c "echo '\n`basename $file .jmod`' ; cat - " ; done } $ findclass /DataSource.class java.activation classes/javax/activation/DataSource.class |
5.2. JDK 9 con módulos
En este punto deberías tener tu código y librerías en el classpath y funcionando. Si no vas a mantener la aplicación puedes
parar aquí. En caso contrario te sugiero estos pasos:
- Empaqueta y ejecuta tu código como módulo automático. Tienes un ejemplo en el holamundo del primer tutorial. No necesitas
cambiar o añadir código, lo único que cambia es el comando javac y java. - Convierte tu código a un módulo no automático. Tras al paso anterior, solo necesitas añadir un descriptor de módulo.
- Si consigues que todo funcione, intenta actualizar las librerías a versiones preparadas para JDK 9.
- Divide tu código en múltiples módulos de acuerdo con tu arquitectura.
Antes de migrar a módulos deberías entender el ejemplo holamundo del que hablé antes, los tres
tipos de módulo, y los tipos de acceso de cada uno.
5.2.1. Posibles problemas
Es posible que alguna de las librerías añadidas como módulo automático necesite jars adicionales. Si usas un gestor de dependencias como Maven, no debería ser un problema porque ya te descarga él todas las dependencias.
Los módulos automáticos exportan todos sus paquetes, pero si no son public y alguna librería quiere hacer reflexión,
tendrás que darle permiso.
- Si el código es tuyo, una manera sencilla es exponer el módulo entero para reflexión con
open module {}
. Luego puedes ir afinando. - Si el código está en una librería externa, tienes que usar los flags de javac y java para permitir la reflexión.
Problemas relacionados con las herramientas que utilizas.
- El soporte de Jigsaw en los plugins de Maven debería irse actualizando
aquí. Si tienes algún problema y entiendes lo que está pasando, probablemente puedas hacer un workaround. - Idea tiene un soporte completo para módulos. Es capaz de mostrar un diagrama con las dependencias de módulos, sugerencias,
inspecciones, etc. En el momento de escribir esto, en la lista de
bugs abiertos había cuatro abiertos que no eran críticos.
6. WTF java.logging
En
Project Jigsaw: JDK modularization destacan java.util.logging como módulo problematico. Si miras el gráfico del año
2000, logging tenía quince flechas entrantes, solo superado por base (java.lang, java.io, java.net, java.nio, security),
que tiene 20. A su vez logging tenía una dependencia circular con management (JMX), que dependía de JNDI, RMI, CORBA,
que a su vez etc. etc. El resultado eran dependencias transitivas que abarcaban la plataforma entera.
Ahora logging tiene cinco flechas entrantes y una saliente. Las dependencias entre módulos se obtienen de sus descripciones. Para mostrar la descripción de un módulo por terminal puedes usar java --describe-module
. Por ejemplo, para ver las dependencias de java.logging
:
1 2 3 4 5 6 7 8 9 |
$ java --describe-module java.logging java.logging@9 exports java.util.logging requires java.base mandated provides jdk.internal.logger.DefaultLoggerFinder with sun.util.logging.internal.LoggingProviderImpl contains sun.net.www.protocol.http.logging contains sun.util.logging.internal contains sun.util.logging.resources |
Para ver las dependencias hacia java.logging:
1 |
for file in $(/usr/libexec/java_home -v 9)/jmods/*; do mod=`basename $file .jmod`; java --describe-module $mod | grep "requires java.logging" | ifne echo $mod | sort; done |
Para que estos comandos bash funcionen necesitas
brew install moreutils
.
Según el gráfico las dependencias parecen:
1 2 3 4 5 |
java.sql java.activation java.xml.crypto java.rmi java.security.sasl |
Pero según el comando de arriba, los siguientes módulos requieren java.logging
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
java.activation java.corba java.rmi java.se java.security.sasl java.sql java.sql.rowset java.xml.bind java.xml.crypto java.xml.ws javafx.fxml javafx.web jdk.deploy jdk.dynalink jdk.javaws jdk.jshell jdk.packager jdk.plugin jdk.policytool jdk.scripting.nashorn jdk.security.jgss jdk.snmp jdk.xml.bind jdk.xml.ws |
La diferencia es que el gráfico de 2017 no muestra javafx, ni jdk, y lo que parece una dependencia transitiva en, por ejemplo,
java.xml.bind
, es en realidad una dependencia directa:
1 2 |
$ java --describe-module java.xml.bind | grep java.logging requires java.logging |
En realidad no es tan malo porque el logging es un caso en el que la información fluye en una única dirección, y java.util.logging
unicamente expone un interfaz que podemos implementar. Ni siquiera es un logging configurable. El Platform Logging API sería buen material para otro artículo.
7. Conclusión
Esta es más o menos toda la teoría sobre el sistema de módulos.
Cosas que no vimos –y por las que preguntaré en mi lecho de muerte con mi último aliento–: internacionalización, layering API, el futuro de OSGi.
Cosas que sí hemos visto:
- Parte 1
- Parte 2
Enlaces donde puedes encontrar más información:
- Project Jigsaw
- The Java Platform Module System (JSR 376) Final release. 2017/8/21
- Oracle JDK 9 Documentation
- JDK 9 Migration Guide
Spring 5 ya soporta Java 9, y los microservicios se benefician de un tamaño más reducido, así que pronto estaremos migrando a Java 9. Si usas Spring boot lee este workaround.
Como al montar en bici, la experiencia se consigue practicando. Cuando encuentres dificultades, espero que estas páginas te sirvan de guía.