Declaración de módulo Java con module-info.java
Declara un módulo Java con module-info.java: requires, exports, opens, uses y provides explicados con ejemplos.
Un módulo se declara en un archivo fuente especial, module-info.java, ubicado en la raíz del árbol de fuentes del módulo (junto al paquete principal, no dentro de él). Se compila a module-info.class. El archivo no contiene código ordinario — solo un bloque module con directivas que describen el límite del módulo.
La estructura del archivo
module com.acme.orders {
requires com.acme.common; // I depend on this module
requires transitive java.sql; // ...and so does anyone who requires me
exports com.acme.orders.api; // public to everyone
exports com.acme.orders.spi to com.acme.web; // public to one module only
opens com.acme.orders.model; // deep reflective access (e.g. for Jackson)
uses com.acme.orders.PricingRule; // I consume this service
provides com.acme.orders.PricingRule // I supply an implementation
with com.acme.orders.StandardPricing;
}El nombre del módulo (com.acme.orders) es un identificador con puntos, convencionalmente el prefijo DNS inverso de los paquetes que contiene. No es un paquete — es su propio espacio de nombres, y dos módulos no pueden compartir un paquete.
requires — declarar dependencias
requires <module> dice "Necesito los paquetes exportados de ese módulo para compilar y ejecutar." El resolutor falla al inicio si falta un módulo requerido. Dos modificadores importantes:
requires transitive— re-exporta la dependencia. Cualquier módulo que te requiera a ti también la leerá automáticamente. Úsalo cuando los tipos de un módulo requerido aparecen en tus propias firmas públicas (un método que devuelve unjava.sql.Connectionobliga a los llamadores a verjava.sql).requires static— una dependencia solo de tiempo de compilación, opcional en tiempo de ejecución (para procesadores de anotaciones, integraciones opcionales).
java.base se requiere implícitamente; nunca se escribe.
exports — declarar tu API pública
exports <package> hace visibles los tipos public de ese paquete para otros módulos. Todo lo que no se exporta está fuertemente encapsulado — invisible aunque sea public. La forma calificada, exports <package> to <module>, <module>, limita la visibilidad a una lista de módulos permitidos, útil para paquetes SPI compartidos solo entre tus propios módulos.
Observa que exports es por paquete, nunca recursivo: exportar com.acme.api no exporta com.acme.api.internal.
opens — permitir reflexión profunda
exports otorga acceso en tiempo de compilación a miembros public. No otorga acceso reflexivo a miembros no públicos. Frameworks como Jackson, Hibernate y Spring usan setAccessible(true) para acceder a campos private — eso requiere opens:
opens <package>— otorga acceso reflexivo en tiempo de ejecución (incluidos miembrosprivate) a todos los módulos.opens <package> to <module>— calificado, solo a módulos nombrados.open module com.acme.orders { … }— abre todos los paquetes (una ayuda de migración contundente).
La diferencia importa: usas exports para un paquete de API, pero usas opens en un paquete de clases de datos que quieres que un serializador pueda inspeccionar sin convertirlo en parte de tu API en tiempo de compilación.
uses / provides — servicios
Estos conectan el patrón ServiceLoader: uses <Service> declara que consumes una interfaz de servicio, y provides <Service> with <Impl> declara una implementación. Tienen su propio capítulo — ver Servicios de módulo — aquí solo observa que viven en el mismo descriptor.
Un ejemplo práctico: construir un descriptor con la API
Normalmente escribes module-info.java y dejas que javac produzca el descriptor. Pero la misma estructura está disponible programáticamente a través de ModuleDescriptor.newModule(...), que es un reflejo fiel de la sintaxis de directivas — construir uno es la forma más clara de ver qué se convierte en cada directiva.
Qué aprender de la ejecución:
- Los nombres de los métodos del constructor se corresponden uno a uno con las directivas:
.requires(...),.exports(...),.opens(...),.uses(...),.provides(...). Leyendo el resultado, el descriptor contiene exactamente la información de unmodule-info.java— prueba de que el archivo es puro metadato, no código ejecutable. - El require de
java.sqlse imprimió con el modificador[TRANSITIVE]mientras quecom.acme.commonse imprimió sin ninguno. Ese modificador es el que re-exportajava.sqla los módulos posteriores; el require simple mantiene la dependencia privada de este módulo. - Los dos exports se imprimieron de forma diferente:
com.acme.orders.apicomo "(to all)" ycom.acme.orders.spicomo "to [com.acme.web]". Un export calificado lleva dentro del descriptor su lista de destinos permitidos — el resolutor la impone, por lo que ningún otro módulo puede leer el paquete SPI. opensapareció en su propia sección, separada deexports. El descriptor mantiene la exposición en tiempo de compilación y el acceso reflexivo en tiempo de ejecución como hechos distintos, razón por la que un serializador necesitaopensincluso cuando el paquete ya está exportado.usesyprovidesse registran junto al resto — las declaraciones de servicio forman parte del límite del módulo, no un archivo de configuración separado como eran en el classpath (META-INF/services). Servicios de módulo convierte estas directivas en unServiceLoaderfuncional.
Errores comunes
- Colocar
module-info.javadentro de un directorio de paquete — debe estar en la raíz del código fuente. - Confundir
exports(tiempo de compilación, miembros públicos) conopens(tiempo de ejecución, todos los miembros). UnJsonMappingExceptionsobre un campo inaccesible casi siempre indica unopensfaltante. - Olvidar
requires transitivecuando tus métodos públicos exponen tipos de otro módulo, obligando a cada llamador a añadir el require manualmente.
El siguiente capítulo, Tipos de módulo, retrocede a los tres tipos de módulo — con nombre, automático y sin nombre — y cómo una aplicación parcialmente modularizada sigue funcionando mientras migras a módulos. Para el panorama general de por qué existe el sistema de módulos, consulta la introducción a los módulos.