Tipos de Módulos Java
Módulos nomeados, automáticos e sem nome em Java e como eles interagem durante a compilação e a execução.
O Java Platform Module System (JPMS) reconhece três tipos de módulo. Apenas um é o módulo "real" que você escreve; os outros dois existem para que os milhões de JARs anteriores ao Java 9 continuem funcionando. Entender em qual tipo um determinado JAR se torna — e que isso depende inteiramente de onde você o coloca — é a chave para migrar sem dor. Esta página define os três tipos, mostra as regras de acesso entre eles e comprova as categorias com um programa executável.
Módulos nomeados
Um módulo nomeado (explícito) é aquele que possui um module-info.class, colocado no module path (--module-path / -p). Ele é o cidadão completo:
- Tem um nome proveniente do seu descritor.
- Lê apenas os módulos que declara em
requires. - Expõe apenas os pacotes que declara em
exports.
Este é o módulo com encapsulamento forte descrito no capítulo sobre declaração de módulos. Tudo que o JPMS promete — dependências declaradas, internos ocultos, resolução com falha rápida — aplica-se aos módulos nomeados.
Módulos automáticos
Um módulo automático é um JAR simples (sem module-info) colocado no module path. O JPMS o envolve em um módulo para que módulos nomeados possam declará-lo em requires durante a migração — sem precisar aguardar o autor da biblioteca adicionar um descritor. Um módulo automático:
- Recebe um nome derivado do nome do arquivo JAR (por exemplo,
guava-32.1.jar→guava), a menos que o manifesto do JAR definaAutomatic-Module-Name. - Exporta todos os pacotes — não possui diretiva
exports, portanto todos os seus pacotes estão abertos para o mundo. - Lê todos os outros módulos, incluindo o módulo sem nome, para que ainda possa enxergar os JARs do classpath.
Ele é uma ponte: permite que você comece a escrever módulos nomeados que dependem de bibliotecas ainda não modularizadas. O custo é que abre mão completamente do encapsulamento, e seu nome derivado automaticamente pode mudar se o JAR for renomeado — motivo pelo qual Automatic-Module-Name no manifesto é o caminho responsável para uma biblioteca adotar.
O módulo sem nome
O módulo sem nome é o repositório geral do classpath. Toda classe carregada do classpath pertence ao módulo sem nome do seu class loader. Ele:
- Não tem nome (
getName()retornanull,isNamed()éfalse). - Lê todos os outros módulos do sistema.
- Exporta todos os seus pacotes para outros módulos sem nome e automáticos.
Mas existe uma parede deliberada em um único sentido: um módulo nomeado não pode declarar requires ao módulo sem nome. Você não pode nomeá-lo e, portanto, não pode depender dele. Esta é a regra que determina a ordem de migração — um módulo nomeado só pode depender de outros módulos nomeados ou automáticos, nunca de código bruto do classpath.
A matriz de acesso
Quem pode ler quem se resume a uma pequena tabela:
| De ↓ / Para → | Nomeado | Automático | Sem nome |
|---|---|---|---|
| Nomeado | somente se requires | somente se requires | nunca |
| Automático | sim | sim | sim |
| Sem nome | sim | sim | sim |
A única célula restritiva — código nomeado não pode alcançar código sem nome — é toda a história de por que a migração é feita de baixo para cima (abordado no próximo capítulo).
Um exemplo prático: identificando o tipo de um módulo em tempo de execução
A API de Module informa, para qualquer classe, se o seu módulo é nomeado e se foi sintetizado automaticamente. Este programa inspeciona três referências — sua própria classe (classpath → sem nome), um tipo do JDK (nomeado) e reporta a camada de boot — para tornar as categorias concretas.
O que concluir da execução:
- A própria classe do programa foi classificada como UNNAMED com nome
null, enquantojava.util.ListeHttpClientforam classificados como NAMED (java.base,java.net.http). Ao executar a partir do classpath, seu código é sempre sem nome; o JDK é sempre um conjunto de módulos nomeados. O tipo de um módulo é determinado por como ele foi carregado, não por nada na própria classe. java.base.canRead(self)retornoufalse, masself.canRead(java.base)retornoutrue. Essa é a parede unidirecional em ação: o módulo sem nome lê tudo, mas nenhum módulo nomeado lê o módulo sem nome. Essa assimetria é precisamente o motivo pelo qual código nomeado não pode declararrequiresao código do classpath.classify()distinguiu automático de nomeado por meio dedescriptor.isAutomatic(). Você não verátrueaqui (nada foi colocado no module path como um JAR simples), mas a verificação é exatamente como as ferramentas relatam um módulo automático — um objeto de módulo real com um descritor sintetizado e totalmente aberto.isExported("java.util")retornoutrue, masisExported("jdk.internal.misc")retornoufalse, mesmo que ambos sejam pacotes reais dentro dejava.base. Oexportsde um módulo nomeado é uma lista de permissões; pacotes não exportados (ou exportados apenas de forma qualificada) são invisíveis para código externo, independentemente de serempublic. O módulo sem nome, por outro lado, exporta tudo que contém.- Nenhum
module-info.javafoi necessário para observar nada disso. As três categorias são fatos de tempo de execução sobre como uma classe foi carregada, egetModule()junto comgetDescriptor()os expõem — as mesmas chamadas que as ferramentas de migração utilizam para descobrir com o que estão trabalhando.
Por que existem três tipos
Os dois tipos de compatibilidade — automático e sem nome — significam que o Java 9+ executa aplicações Java 8 sem modificações. Você opta pelo encapsulamento forte um JAR por vez: deixe tudo no classpath (tudo sem nome) e nada muda; mova uma biblioteca para o module path sem um descritor e ela se torna automática; adicione um module-info.java e ela se torna nomeada. A seguir, os serviços de módulos apresentam o mecanismo uses/provides que desacopla módulos, e a migração de módulos conduz um projeto real por esses três estados.