Java 9 modules

De ChuWiki


¿Qué son los módulos de Java 9?[editar]

Los módulos de java 9 son librerías jar que no exponen todas sus clases al exterior, sino solo aquellas que el diseñador del módulo ha deseado.

Por ejemplo, si dentro una librería jar tenemos los paquetes com.chuidiang.ejemplos.interfaces y com.chuidiang.ejemplos.implementacion, podeos exponer al exterior sólo las clase en el paquete interfaces y ocultar las clases en el paquete implementación.

Decimos módulos de Java 9 porque este concepto apareció en la versión 9 de Java. Pero cualquier versión superior de Java también la tiene.

¿Para qué sirven los módulos en java 9?[editar]

Los módulos de java 9 ayudan a hacer un diseño más organizado de nuestras aplicaciones. Si nuestro proyecto consta de varias librerías (módulos) y en cada librería indicamos qué se puede exponer al exterior para ser usado y qué queda oculto solo para uso en la propia librería, se ayuda a lo siguiente:

  • Encapsulación. Cada módulo expone sólo expone lo que ofrece y oculta los detalles de su implementación. Esto facilita el mantenimiento del módulo, ya que podemos tocar la parte oculta con la certeza de que no va a afectar u obligar a recompilar otras partes de la aplicación que usan este módulo.
  • Menos acoplamiento. Si solo usamos la parte pública de un módulo, siempre dependeremos menos de él y podremos sustituirlo por otro que si usamos absolutamente todas las clases que tiene dicho módulo.

¿Cómo se hace un módulo?[editar]

Un módulo no es más que una librería java normal con la diferencia de que tiene un fichero module-info.java en el directorio raíz de la librería. Si src es el directorio donde están nuestro fuentes java

src
 + com/chuidiang/ejemplos/interfaces/UnaInterface.java
 + com/chuidiang/ejemplos/implementations/UnaImplementacion.java
 + module-info.java

Vemos simplemente un par de paquetes normales con sus clase java normales y el fichero module-info.java, sin paquete, en el directorio de fuentes. Su presencia convierte a esta librería en un módulo.

Contenido de module-info.java[editar]

El contenido mínimo imprescindible de module-info.java es el nombre que queremos dar al módulo, como el siguiente ejemplo

module miproyecto.mimodulo {
}

donde miproyecto.mimodulo es el nombre que queramos dar al módulo. Puede ser cualquier nombre, aunque suele ser buena idea seguir alguna nomenclatura para evitar que coincidan los nombres de los módulos por equivocación, sobre todo si pensamos reutilizarlos en distintos proyectos. En el ejemplo hemos puesto miproyecto que sería el proyecto en el que se ha creado el módulo, un punto y el nombre del módulo en sí mismo. Cualquier nomenclatura que quieras adoptar es buena.

Exportar paquetes del módulo[editar]

Tal cual lo tenemos, no exportamos nada. El módulo no podría ser utilizado por otros módulos. Lo suyo es que aquí indiquemos qué paquetes exportamos de la siguiente forma

module miproyecto.mimodulo {
   exports com.chuidiang.ejemplos.interfaces;
}

Con esto hacemos públicas todas las clases dentro del paquete com.chuidiang.ejemplos.interfaces

Importar paquetes de otro módulo[editar]

Si quremos que otro módulo pueda utilizar las clase públicas de este módulo, en su fichero module-info.java debemos poner que queremos usarlas. El siguiente sería el contenido de module-info.java de este segundo módulo

module miproyecto.miotromodulo {
   requires miproyecto.mimodulo;
}

Estamos indicando que queremos usar el módulo miproyecto.mimodulo. Con esto podremos usar lo que ese módulo haya hecho público (exporte).

Ofrecer y usar servicios con módulos[editar]

Suele ser habitual que un módulo exporte interfaces y que las implementaciones de esas interfaces queden ocultas. Si queremos usar las implementaciones de esas interfaces, tenemos el problema de que están ocultas y no podemos instanciarlas. Así que tiene que haber una forma de poner obtener esa instancia sin verla

Para ello, el módulo que ofrece la implementación (el servicio), en su module-info.java debe indicarlo

module miproyecto.mimodulo {
   exports com.chuidiang.ejemplos.interfaces;
   provides com.chuidiang.ejemplos.interfaces.UnaInterface with com.chuidiang.ejemplos.implementations.UnaImplementacion
}

Con provides indicamos que estamos ofreciendo una implementacion de UnaInterface con la clase UnaImplementacion. Cuando ejecutemos, java se encargará de instanciar esta clase y dársela al que la necesite. Vemos como indicarlo.

En el módulo que queremos usar la implementación, debemos poner

module miproyecto.miotromodulo {
   requires miproyecto.mimodulo;
   uses com.chuidiang.ejemplos.interfaces.UnaInterface
}

Con uses indicamos que vamos a querer usar las implementaciones de UnaInterface.

Una vez hecho esto, lo tenemos todo listo. La clase que necesita usar la implementación, debe obtenerla con la clase ServiceLoader de Java, como el siguiente trozo de código

ServiceLoader<UnaInterface> services = ServiceLoader.load(UnaInterface.class);

services.iterator().forEachRemaining(service -> 
   // service es de tipo UnaInterface, podemos guardarla, o usarla, o lo que necesitemos. Tras ella está la implementación.
);

Módulos del sistema[editar]

En el momento que nuestra librería o aplicación la hacemos con módulos, usando modele-info.java, no tendremos disponibles por defecto todas las clases propias de Java. El mismo java está partido en módulos y algunos debemos indicar explicitamente que queremos usarlos. Por ejemplo, las clases de javax.swing para interfaces gráficas no están disponibles por defecto. Están en el módulo del sistema java.desktop. Así que si queremos usar javax.swing, debemos poner

module miproyecto.mimodulo {
   requires java.desktop;
}

El comando java --list-modules nos da un listado de todos los módulos del sistema.