Mark Reinhold, principal arquitecto de Oracle de Java SE, ha publicado un documento con una primera especificación del sistema de módulos que va a incorporar la plataforma en la siguiente revisión mayor. Los módulos son mecanismos para especificar para un conjunto de paquetes qué clases de entre las definidas públicas en esos paquetes se van a exportar. Podemos verlo como una forma de definir una interfaz para un conjunto de paquetes.
A menudo dentro de la plataforma Java sucede que una librería internamente se organiza en paquetes, y para poder usar una clase desde otro paquete de la misma librería esa clase tiene que hacerse pública. Sin embargo, el creador de la librería no tenía intención de que esa clase formase parte de la interfaz de su librería. Esa clase era un detalle de implementación. Pero al hacerla pública es posible emplearla desde fuera del código de la librería. El sistema de módulos pretende resolver este problema. Además, pretende permitir definir de un modo modular los componentes del JDK, permitiendo crear JRE que no incluyan todas las partes de la plataforma y sean más ligeros.
A continuación vamos a hacer un pequeño resumen del documento publicado por Mark.
Un módulo debe declarar que otros módulos requiere para poder ser compilado y para ejecutarse. También debe declarar que paquetes dentro del módulo son exportados como su interfaz. Por ejemplo:
module com.foo.bar { requires com.foo.baz; com.foo.bar.alpha; com.foo.bar.beta; }
Tanto la declaración de los módulos que requiere un módulo, como de los paquetes que exporta, es opcional. La definición más básica de un módulo es por tanto:
module com.foo.bar { }
El código fuente de la declaración de un módulo por convenio se pondrá en un archivo con nombre module-info.java que se encontrara en la raíz de la jerarquía de paquetes donde comienza el módulo. Dicho archivo será compilado, como cualquier archivo Java, a un archivo .class.
Los módulos se podrán empaquetar en archivos jar especiales que se denominarán "jar modulares" ("modular JAR files"). Estos archivos eran exactamente iguales que los archivos jar actuales, sólo que en su raíz deben contener el archivo module-info.class. Por ejemplo, un jar modular podría contener:
META-INF/ META-INF/MANIFEST.MF module-info.class com/foo/bar/alpha/AlphaFactory.class com/foo/bar/alpha/Alpha.class
De este modo se consigue compatibilidad hacia atrás. Las herramientas actuales simplemente ignoran el archivo de declaración de módulo. Oracle advierte en este documento que para modularizar la implementación de referencia van a introducir un artefacto diferente a los archivos jar que, entre otras cosas, va a permitir contener también código nativo y archivos de configuración. Todavía está por definir si este formato alternativo para los módulos, que ahora mismo se llama provisionalmente JMOD, será objeto de estandarización o será simplemente un detalle de implementación del JDK de Oracle.
En Java 9 una implementación de Java SE podrá contener todos o parte de los módulos de la plataforma. El único módulo que va a ser obligatorio va a ser el módulo java.base, cuyo contenido principal es:
module java.base { exports java.io; exports java.lang; exports java.lang.annotation; exports java.lang.invoke; exports java.lang.module; exports java.lang.ref; exports java.lang.reflect; exports java.math; exports java.net; ... }
Aunque todavía no es completamente seguro, probablemente habrá otros módulos independientes que contengan java.sql.*, java.xml.* y java.logging.*, entre otros.
Cuando un módulo depende de otro, indirectamente las dependencias del segundo también son necesarias para el primero. Así por ejemplo, si tenemos el módulo:
module com.foo.app { requires com.foo.bar; requires java.sql; }
Y a su vez el módulo java.sql ha sido definido como:
module java.sql { requires java.logging; requires java.xml; exports java.sql; exports javax.sql; exports javax.transaction.xa; }
El módulo com.foo.app dependerá de modo indirecto también de los módulos de los que depende java.sql. La siguiente imagen mostraría el grafo de dependencias, donde las líneas azules oscuro representan dependencias explícitas, mientras que las claras representan dependencias implícitas:
Cuando hay una dependencia explícita entre dos módulos, el primero necesita tener capacidad para leer el código del segundo ya que lo va a usar. Esto no tiene que ser siempre así cuando hay una dependencia implícita. Por defecto, un módulo no puede ver el código de otros módulos diferentes de aquellos que ha declarado en sus "requires".
Sin embargo, hay una situación en la que esto podría ser problemático. Cuando un módulo B depende de otro C, y los métodos de B emplean en su declaración clases de C (por ejemplo, devuelve un objeto de una clase definida en C) , si tenemos un tercer módulo A que emplea de modo explícito a B, necesariamente A también va a tener que conocer la existencia de C. En este caso, es necesario algún mecanismo para forzar una dependencia entre A y C que no requiera que necesariamente el programador de A tenga que conocer esta relación; esta dependencia es necesaria para que A tenga acceso al código fuente de C y pueda compilar y ejecutarse.
Esta situación debe distinguirse de la situación en la cual B depende de C, pero la interfaz de B no exporta ninguno de sus tipos. Si A depende de B, A no necesita para nada "leer" el código de C. Podemos compilar sin problemas A sin tener el código de C. En este caso A seguiría dependiendo implícitamente de C, pero no es necesaria una dependencia explícita que otorgaría a A permiso de lectura sobre el código de C.
Para resolver esto, al indicar los módulos que requiere un módulo determinado se puede emplear la palabra "public" después de "requires". Ese modificador quiere decir que cuando un módulo requiera el módulo que ha declarado dependencias de tipo "requires public", las dependencias "requires public" también se convertirán en dependencias explícitas del primero. Es decir, si tenemos:
module java.sql { requires public java.logging; requires public java.xml; exports java.sql; exports javax.sql; exports javax.transaction.xa; }
y:
module com.foo.app { requires java.sql; }
El módulo com.foo.app tendrá una dependencia explícita, y por tanto podrá acceder al código de, java.xml y java.logging. Su grafo de dependencias se muestra bajo estas líneas; observemos como ahora hay flechas adicionales en azul oscuro que indican dependencias explícitas; dos de ellas, las conectadas por líneas verdes con java.sql, no fueron declaradas explícitamente en la definición del módulo (como puede verse en el código sobre este párrafo).
En Java 9 todas las clases pertenecerán a un módulo. Para mantener compatibilidad hacia atrás, si un cargador de clases carga una clase que no pertenezca a ningún módulo, esa clase pertenecerá al módulo "sin nombre" ("unnamed module").
Este es un resumen del documento original; el documento original tiene bastantes más detalles que no he abordado en este pequeño resumen. Podéis consultarlo aquí. Por ejemplo, se cubren otros aspectos como qué pasa con los cargadores de clases y Con el API de reflection. Pero creo que los aspectos cubiertos en este artículo van a ser los que necesiten el 90 por ciento de los programadores. Mi impresión es que es un buen diseño y es relativamente sencillo.
¿Qué opinión tienes tú sobre este primer borrador de especificación del sistema de módulos de Java 9? ¿Te convence o no te convence?