Novedades y nuevas características de Java 9, los módulos

Publicado por pico.dev el , actualizado el .
blog-stack java planeta-codigo programacion
Comentarios

Si en Java 8 la característica más destacada fue la incorporación al lenguaje de las lambdas y los streams en Java 9 la característica que más destaca es la definición de los módulos que proporciona varios importantes beneficios.

Java

Después de unos cuantos aplazamientos de fechas finalmente ha sido publicado el 21 de septiembre de 2017 la versión 9 del lenguaje y plataforma Java, tres años después de las también importantes novedades y nuevas características de Java 8. Al mismo tiempo se ha publicado versión de Java EE 8.

La incorporación de los módulos a la plataforma con Java 9 es una de las modificaciones más importantes en esta versión mayor del lenguaje. Aún siendo una de las características más destacadas y que ha eclipsado a otras modificaciones más allá de los módulos también importantes.

Duke Java 9

Los módulos van a mejorar una de las deficiencias existentes en la visibilidad de las clases entre paquetes. Los módulos de Java proporcionan una mayor encapsulación de las clases contenidas en un paquete y las librerías. Esta encapsulación evita que una aplicación u otra librería haga uso y dependa de clases y paquetes de los que no debería lo que mejora la compatibilidad con versiones futuras. Los desarrolladores de una librería con los módulos ahora tienen un mayor control de los paquetes que expone una librería y que forma parte de su API pública. Con lo que se evita casos que se han dado hasta ahora como que librerías y programas dependan de clases internas en la API de Java como sun.misc.BASE64Encoder o la famosa sun.misc.Unsafe, para la primera en Java se añadió un reemplazo con java.util.Base64, para la segunda con Java 9 para parte de su funcionalidad se ha añadido algunas nuevas clases.

Java 9 y JShell

Los módulos proporcionan:

  • Encapsulación fuerte: se diferencia entre que es la API pública y usable y la parte privada a la que impide su uso accidental y acoplamiento indeseado entre módulos. La parte privada está encapsulado y de esta forma puede modificarse libremente con la seguridad de no afectar a los usuarios del módulo.
  • Interfaces bien definidas: el código no encapsulado forma parte de la API del módulo, dado que otros módulos pueden usar esta API pública hay que tener especial cuidado al modificarlo al introducir cambios que sean incompatibles. Los módulos deben exportar una API bien definida y estable.
  • Dependencias explícitas: los módulos necesitan a menudo otros módulos, estas dependencias son parte de la definición del módulo. Las dependencias explícitas forman un grafo que es importante conocer para entender las necesidades de una aplicación y para ejecutarla con todas sus dependencias.

Los beneficios son:

  • Configuración confiable: el sistema de módulos comprueba si una combinación de módulos satisface todas las dependencias antes de compilar o ejecutar una aplicación.
  • Encapsulación fuerte: se evitan dependencias sobre detalles internos de implementación.
  • Desarrollo escalable: se crean límites entre el equipo que desarrolla un módulo y el que lo usa.
  • Optimización: dado que el sistema de módulos sabe que módulos necesita cada uno solo se consideran los necesarios mejorándose tiempos de inicio y memoria consumida.
  • Seguridad: la encapsulación y optimización limita la superficie de ataque.

La modularización afecta al diseño, compilación, empaquetado y despliegue es mucho más que una nueva característica del lenguaje. Los módulos son artefactos con su propia entidad que contienen código y metadados para describir el módulo y como se relaciona con otros módulos.

Hasta ahora se seguía una convención de poner clases en paquetes de nombre .impl o .internal pero realmente la gente seguía usando esas clases porque simplemente se podía. No había ninguna forma de ocultar las implementaciones de esos paquetes más allá del los modificadores de accesibilidad protected y private que no son satisfactorios para ocultar las implementaciones.

Java desde sus inicios ha hecho un buen trabajo en la definición de interfaces usando la palabra reservada interface. En el apartado de dependencias es donde había deficiencias. Sí, hay sentencias import explícitas pero desafortunadamente son únicamente para el tiempo de compilación.

En tiempo ejecución no hay ninguna noción de archivos JAR o agrupación lógica. En el classpath todas las clases son puestas en una lista plana. Cuando la JVM carga una clase la encuentra recorriendo esa lista en orden secuencial, tan pronto como la clase es encontrada la búsqueda finaliza y la clase es cargada. Si la clase no se encuentra se obtiene una excepción en tiempo de ejecución y dado que las clases son cargadas bajo demanda en el momento de uso esa excepción potencialmente puede ser lanzada en un momento posterior de haber iniciado la aplicación. La JVM no puede verificar eficientemente la corrección del classpath en el inicio o si se debería añadir otra librería jar. Otros problemas insidiosos suceden cuando hay clases duplicadas en el classpath por versiones diferentes de una misma librería.

Antes del sistema de módulos de Java la librería de tiempo de ejecución consistía en un gran archivo rt.jar con un tamaño de más de 60 MiB. Este archivo contiene la mayor parte de clases de la plataforma en forma de monolito. Para conseguir mayor flexibilidad y ser una plataforma de futuro se decidió modularizar el JDK.

Eliminar algunas tecnologías en desuso del JDK no era una opción viable. La compatibilidad hacia atrás es uno de los principios más importantes para Java que guían su desarrollo. Eliminar estas APIs rompería esta compatibilidad hacia atrás, a pesar de que afectaría a un pequeño porcentaje de usuarios todavía hay una buena cantidad de gente usando tecnologías como CORBA.

Descomponer el JDK en módulos ha sido un trabajo inmenso. Con más de 20 años de código heredado acumulados separar una enmarañada y grande base de código conteniendo cientos de clases en módulos bien definidos con límites claros mientras se mantiene la compatibilidad hacia atrás. Esto toma tiempo siendo el motivo de tomar tanto tiempo el incorporar un sistema de módulos en Java. Pero en el futuro este esfuerzo será recompensado en términos de velocidad de desarrollo y aumento de flexibilidad para el JDK.

Con el tiempo las dependencias entre los propios paquetes y clases de la API de Java estaba enmarañada, con Java 9 las dependencias entre paquetes se ha simplificado en gran medida.

Módulos de Java 8 y Java 9

El entorno de ejecución de Java y el compilador conocen exactamente ahora que módulo resolver al buscar los tipos para un paquete dado. Previamente la única forma de obtener un tipo arbitrario era hacer una búsqueda en todo el classpath. Por ejemplo, dos módulos con el mismo nombre producen un error en inicio de la aplicación, en vez de en tiempo de ejecución.

Los módulos permiten definir a cada librería los paquetes de clases que exporta como su API accesible por otra librería o programa que la requiera. Además, cada librería debe al mismo tiempo definir qué paquetes requiere. Las exportaciones y requerimientos permiten ahora detectar al iniciar la máquina virtual si el grafo de dependencias está completo cosa que antes se producía en un mayor número de casos en tiempo de ejecución posiblemente con la excepción NoClassDefFound. Una de los efectos que se mejoran en Java y que ya es una característica a la que se le da mucha importancia es la compatibilidad hacia atrás y también la encapsulación ya que los desarrolladores de las librerías tienen mayor control de que paquetes se permite su uso evitando dependencias no deseadas que impidan en un futuro que aplicaciones que hipotéticamente las usasen dejasen de ser compatibles con nuevas versiones.

La definición de un módulo se realiza con un nuevo archivo de código fuente de nombre module-info.java. Con la palabra reservada requires y una línea por paquete se definen qué paquetes requiere el módulo, con la palabra reservada exports se define que paquetes del módulo se exportan y son visibles por algún otro módulo que lo requiera. También se han añadido las palabras reservadas provides y uses para proporcionar y usar definiciones de servicios que con anterioridad se realizaba en archivos ubicados en META-INF/services como muestro en el ejemplo Aplicación Java extensible con la clase ServiceLoader. También se puede hacer que la directiva requires sea de forma transitiva para que el módulo que lo use pueda usar ese paquete sin requerirlo de forma explícita, la directiva opens permite hacer uso de reflectividad usando el método setAccesible.

Dado que la transición hacia el uso de los módulos puede generar problemas de compatibilidad con aplicaciones existentes se han añadido algunos parámetros para la máquina virtual en el comando java e incluso en el caso más grave desactivar completamente el sistema de módulos, aunque lógicamente esto está desaconsejado. En la guía de migración a Java 9 están detallados los aspectos a tener en cuenta en la migración de una versión anterior a Java 9.

Este es el típico ejemplo Hola Mundo con Java 9 en que que muestro como compilar un programa usando los módulos y como ejecutarlo directamente desde la linea de comandos. En el código de la clase Main no hay ningún cambio respecto al que sería con una versión anterior de Java sin embargo se añade el nuevo archivo de código fuente module-info.java donde se definen sus dependencias que este programa no tiene salvo la implícita sobre el módulo java.base. Los comandos para compilar y ejecutar el ejemplo directamente con los comandos javac y java si cambian, ahora se usa en vez de classpath la opción module-path y se indica la clase del módulo que contiene el método main del programa.

Hola Mundo con Java 9

El comando jdeps muestra las dependencias de los módulos muy útil para tareas de análisis o depuración.

Análisis de dependencais con jdeps

Para profundizar más en los detalles de la modularidad y el resto de novedades de Java 9 están los libros Java 9 Revealed y Java 9 Modularity Revealed.

Para finalizar este artículo incluyo un vídeo sobre los módulos que comenta los aspectos más destacados. Hay otros vídeos sobre las novedades de Java 9 de no más de 15 minutos cada uno.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./java.sh.

Yo apoyo al software libre