Declaração Java module-info.java
Declare um módulo Java com module-info.java — requires, exports, opens, uses, provides.
Um módulo é declarado em um arquivo-fonte especial, module-info.java, colocado na raiz da árvore de fontes do módulo (ao lado do pacote principal, não dentro dele). Ele é compilado para module-info.class. O arquivo não contém código comum — apenas um bloco module listando diretivas que descrevem a fronteira do módulo.
A estrutura do arquivo
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;
}O nome do módulo (com.acme.orders) é um identificador pontilhado, convencionalmente o prefixo DNS reverso dos pacotes que ele contém. Ele não é um pacote — é seu próprio namespace, e dois módulos não podem compartilhar um pacote.
requires — declarando dependências
requires <module> significa "preciso dos pacotes exportados desse módulo para compilar e executar." O resolvedor falha na inicialização se um módulo requerido estiver ausente. Dois modificadores importantes:
requires transitive— reexporta a dependência. Qualquer módulo que requeira você automaticamente também a lê. Use-o quando os tipos de um módulo requerido aparecem nas suas próprias assinaturas públicas (um método que retorna umjava.sql.Connectionforça os chamadores a enxergarjava.sql).requires static— uma dependência somente em tempo de compilação, opcional em tempo de execução (para processadores de anotação, integrações opcionais).
java.base é requerido implicitamente; você nunca o escreve.
exports — declarando sua API pública
exports <package> torna os tipos public desse pacote visíveis a outros módulos. Tudo que não for exportado está fortemente encapsulado — invisível mesmo sendo public. A forma qualificada, exports <package> to <module>, <module>, restringe a visibilidade a uma lista de permissões nomeada, útil para pacotes SPI compartilhados apenas entre seus próprios módulos.
Note que exports é por pacote, nunca recursivo: exportar com.acme.api não exporta com.acme.api.internal.
opens — permitindo reflexão profunda
exports concede acesso em tempo de compilação a membros public. Ele não concede acesso reflexivo a membros não públicos. Frameworks como Jackson, Hibernate e Spring usam setAccessible(true) para alcançar campos private — isso requer opens:
opens <package>— concede acesso reflexivo em tempo de execução (incluindo membrosprivate) a todos os módulos.opens <package> to <module>— qualificado, apenas para módulos nomeados.open module com.acme.orders { … }— abre todos os pacotes (um auxílio de migração abrangente).
A distinção importa: você exports um pacote de API, mas usa opens em um pacote de classes de dados que deseja que um serializador reflita sem torná-lo parte da sua API em tempo de compilação.
uses / provides — serviços
Esses conectam o padrão ServiceLoader: uses <Service> declara que você consome uma interface de serviço, e provides <Service> with <Impl> declara uma implementação. Eles têm seu próprio capítulo — veja Serviços de módulo — aqui apenas note que eles residem no mesmo descritor.
Um exemplo trabalhado: construindo um descritor com a API
Normalmente você escreve module-info.java e deixa o javac produzir o descritor. Mas a mesma estrutura está disponível programaticamente por meio de ModuleDescriptor.newModule(...), que é um espelho fiel da sintaxe de diretivas — construir um é a maneira mais clara de ver o que cada diretiva se torna.
O que observar na execução:
- Os nomes dos métodos do construtor correspondem um a um com as diretivas:
.requires(...),.exports(...),.opens(...),.uses(...),.provides(...). Lendo a saída, o descritor é exatamente a informação em ummodule-info.java— prova de que o arquivo é metadado puro, não código executável. - O
requiredejava.sqlfoi impresso com um modificador[TRANSITIVE]enquantocom.acme.commonfoi impresso sem nenhum. Esse modificador é o que reexportajava.sqlpara módulos downstream; orequiresimples mantém a dependência privada para este módulo. - As duas exportações foram impressas de forma diferente:
com.acme.orders.apicomo "(to all)" ecom.acme.orders.spicomo "to [com.acme.web]". Uma exportação qualificada carrega sua lista de permissões de destino dentro do descritor — o resolvedor a aplica, portanto nenhum outro módulo pode ler o pacote SPI. opensapareceu em sua própria seção, separado deexports. O descritor mantém a exposição em tempo de compilação e o acesso reflexivo em tempo de execução como fatos distintos, e é por isso que um serializador precisa deopensmesmo quando o pacote já está exportado.useseprovidessão registrados ao lado do restante — as declarações de serviço fazem parte da fronteira do módulo, não um arquivo de configuração separado como eram no classpath (META-INF/services). Serviços de módulo transforma essas diretivas em umServiceLoaderfuncional.
Erros comuns
- Colocar
module-info.javadentro de um diretório de pacote — ele deve ficar na raiz do código-fonte. - Confundir
exports(tempo de compilação, membros públicos) comopens(tempo de execução, todos os membros). UmJsonMappingExceptionsobre um campo inacessível quase sempre significa umopensausente. - Esquecer
requires transitivequando seus métodos públicos expõem tipos de outro módulo, forçando cada chamador a adicionar orequiremanualmente.
O próximo capítulo, Tipos de módulo, recua para os três tipos de módulo — nomeado, automático e não nomeado — e como uma aplicação parcialmente modularizada continua funcionando enquanto você migra para módulos. Para uma visão mais ampla de por que o sistema de módulos existe, veja a introdução aos módulos.