W3docs

Introducción a los módulos de Java (JPMS)

Qué son los módulos en Java, los problemas que resuelve JPMS y cómo se relaciona con el classpath.

El Java Platform Module System (JPMS), introducido en Java 9, añade una capa por encima de los paquetes. Un módulo es un grupo de paquetes con nombre y autodescriptivo que declara explícitamente dos cosas: qué necesita de otros módulos y qué ofrece a ellos. Esa declaración reside en un único archivo, module-info.java, en la raíz del módulo. Esta parte del libro lo explica en detalle; este capítulo explica por qué existe.

El problema que JPMS resuelve: el classpath

Antes de Java 9, todos los JAR se colocaban en un único classpath plano. Ese esquema tenía problemas crónicos:

  • Sin encapsulación. Cada clase public de cada JAR era accesible para todos. Una clase pensada solo como ayudante interno (sun.misc.Unsafe, com.example.internal.*) podía ser usada por cualquiera, por lo que nunca podía cambiarse de forma segura.
  • Sin dependencias declaradas. Un JAR nunca indicaba qué otros JAR necesitaba. Una dependencia faltante solo se descubría cuando un NoClassDefFoundError explotaba en tiempo de ejecución — posiblemente en producción.
  • JAR hell. Dos JAR que proporcionaban el mismo paquete, o dos versiones de la misma biblioteca, se fusionaban silenciosamente según el orden del classpath. Ganaba la clase que se cargara primero.

Los módulos atacan los tres problemas: un módulo oculta cada paquete que no exporta explícitamente, declara cada módulo que requires, y la JVM verifica el grafo completo al arrancar — los módulos faltantes o duplicados fallan rápidamente.

Módulos vs. paquetes vs. JAR

Estos tres conceptos son fáciles de confundir:

ConceptoLo que agrupaRegla de visibilidad
Packageclasespublic/protected/package-private dentro del JAR
JARpackages + recursostodo lo que es public es visible en el classpath
Modulepackagessolo los packages exported son visibles para otros módulos

Un módulo suele empaquetarse como un JAR (un "modular JAR" — un JAR ordinario con un module-info.class en su raíz). La diferencia es el descriptor: coloca el JAR en el classpath y las reglas se ignoran; ponlo en el module path y JPMS las hace cumplir.

Encapsulación fuerte, en una frase

La regla principal: un package es invisible para otros módulos a menos que su módulo lo exports, incluso si sus clases son public. public ahora significa "accesible para el código que puede leer este package", y leer un package requiere un exports junto con un requires. Por eso el propio JDK pudo finalmente ocultar sus internos: java.base exporta java.util pero no jdk.internal.misc.

El JDK también es modular

Desde Java 9, el JDK está dividido en ~70 módulos (java.base, java.sql, java.xml, java.net.http, …). java.base es especial: todo módulo lo requiere implícitamente y contiene los elementos esenciales del lenguaje (java.lang, java.util, java.io). Cada clase que hayas usado alguna vez vive en uno de estos módulos, algo que el ejemplo práctico a continuación hace visible.

Un ejemplo práctico: inspeccionar módulos en tiempo de ejecución

No necesitas escribir un módulo para ver módulos: la API Module en tiempo de ejecución informa el módulo al que pertenece cualquier clase. Este programa pregunta a varias clases a qué módulo pertenecen, comprueba su propio módulo y echa un vistazo a la capa de arranque con la que la JVM inició.

java— editable, runs on the server

Lo que se desprende de la ejecución:

  • String, ArrayList y HttpClient informaron java.base, java.base y java.net.http. Cada clase pertenece exactamente a un módulo, y getModule() indica cuál — los tipos del lenguaje viven todos en java.base, mientras que HttpClient está en su propio módulo, que habría que declarar con requires java.net.http para usarlo.
  • La clase del propio programa informó isNamed() == false y un getName() de null. El código ejecutado desde el classpath cae en el módulo sin nombre, un contenedor de compatibilidad que no requiere nada explícitamente y puede leer todos los demás módulos. Por eso los programas de classpath siguen compilando y ejecutándose sin cambios en Java 9+.
  • ModuleLayer.boot() expuso el grafo de módulos que la JVM resolvió al arrancar — contar los java.* demuestra que el JDK realmente está dividido en muchos módulos, no en un monolito.
  • java.base no está abierto (isOpen() == false) pero exporta muchos packages; expone java.lang y java.util a todos mientras mantiene oculto jdk.internal.*. Exportar un package y abrir un módulo son interruptores distintos — un capítulo posterior vuelve sobre opens.
  • Nada aquí requirió un module-info.java. La API Module es metadatos reflexivos disponibles para cualquier programa; escribir tu propio módulo (siguiente capítulo) es lo que te permite declarar estas reglas en lugar de solo observar las del JDK.

Qué cubre el resto de esta parte

  • module-info.java — las directivas: requires, exports, opens, uses, provides.
  • Tipos de módulos — módulos con nombre, automáticos y sin nombre, y cómo se combinan.
  • Servicios — desacoplar una interfaz de su implementación con uses/provides y ServiceLoader.
  • Migración — mover una aplicación de classpath existente al module path sin una reescritura completa.

Los módulos son opcionales: una aplicación Java puede ejecutarse indefinidamente en el classpath. Pero comprenderlos explica el JDK moderno, desbloquea los runtimes personalizados con jlink y da a las bibliotecas una encapsulación real. El siguiente capítulo, la declaración module-info.java, escribe el descriptor que convierte a un módulo en módulo.

Práctica

Práctica
Una biblioteca JAR contiene una clase 'public' en el package 'com.acme.internal' que los autores destinan solo al uso interno de la biblioteca. El JAR se construye como un modular JAR cuyo 'module-info.java' exporta 'com.acme.api' pero no 'com.acme.internal'. ¿Qué ocurre cuando este JAR se coloca en el MODULE PATH y otro módulo intenta importar 'com.acme.internal.Helper'?
Una biblioteca JAR contiene una clase 'public' en el package 'com.acme.internal' que los autores destinan solo al uso interno de la biblioteca. El JAR se construye como un modular JAR cuyo 'module-info.java' exporta 'com.acme.api' pero no 'com.acme.internal'. ¿Qué ocurre cuando este JAR se coloca en el MODULE PATH y otro módulo intenta importar 'com.acme.internal.Helper'?
Was this page helpful?