W3docs

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 transitivere-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 un java.sql.Connection obliga a los llamadores a ver java.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 miembros private) 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.

java— editable, runs on the server

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 un module-info.java — prueba de que el archivo es puro metadato, no código ejecutable.
  • El require de java.sql se imprimió con el modificador [TRANSITIVE] mientras que com.acme.common se imprimió sin ninguno. Ese modificador es el que re-exporta java.sql a 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.api como "(to all)" y com.acme.orders.spi como "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.
  • opens apareció en su propia sección, separada de exports. 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 necesita opens incluso cuando el paquete ya está exportado.
  • uses y provides se 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 un ServiceLoader funcional.

Errores comunes

  • Colocar module-info.java dentro de un directorio de paquete — debe estar en la raíz del código fuente.
  • Confundir exports (tiempo de compilación, miembros públicos) con opens (tiempo de ejecución, todos los miembros). Un JsonMappingException sobre un campo inaccesible casi siempre indica un opens faltante.
  • Olvidar requires transitive cuando 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.

Práctica

Práctica
Un módulo 'com.acme.orders' tiene un método público que devuelve un 'java.sql.Connection'. Los llamadores de otros módulos no pueden compilar porque no ven el tipo 'java.sql.Connection', aunque ya tienen 'requires com.acme.orders'. ¿Qué directiva en 'com.acme.orders' soluciona esto sin obligar a cada llamador a añadir 'requires java.sql' manualmente?
Un módulo 'com.acme.orders' tiene un método público que devuelve un 'java.sql.Connection'. Los llamadores de otros módulos no pueden compilar porque no ven el tipo 'java.sql.Connection', aunque ya tienen 'requires com.acme.orders'. ¿Qué directiva en 'com.acme.orders' soluciona esto sin obligar a cada llamador a añadir 'requires java.sql' manualmente?
Was this page helpful?