Introdução aos Módulos Java (JPMS)
O que são módulos em Java, os problemas que o JPMS resolve e como ele se relaciona com o classpath.
O Java Platform Module System (JPMS), introduzido no Java 9, adiciona uma camada acima dos pacotes. Um módulo é um grupo nomeado e autodescritivo de pacotes que declara explicitamente duas coisas: o que precisa de outros módulos e o que oferece a eles. Essa declaração reside em um único arquivo, module-info.java, na raiz do módulo. Esta parte do livro percorre esse tema; este capítulo explica por que ele existe.
O problema que o JPMS resolve: o classpath
Antes do Java 9, todos os JARs eram lançados em um único classpath plano. Esse arranjo tinha problemas crônicos:
- Sem encapsulamento. Toda classe
publicem todos os JARs era acessível a qualquer um. Uma classe criada apenas como helper interno (sun.misc.Unsafe,com.example.internal.*) podia ser usada por qualquer pessoa, portanto nunca poderia ser alterada com segurança. - Sem dependências declaradas. Um JAR nunca informava quais outros JARs ele precisava. Você descobria uma dependência ausente somente quando um
NoClassDefFoundErrorexplodía em tempo de execução — possivelmente em produção. - JAR hell. Dois JARs fornecendo o mesmo pacote, ou duas versões da mesma biblioteca, eram silenciosamente mesclados na ordem do classpath. A primeira classe carregada vencia.
Os módulos atacam todos os três: um módulo oculta todos os pacotes que não exporta explicitamente, declara todos os módulos que requires, e a JVM verifica o grafo inteiro na inicialização — módulos ausentes ou duplicados falham rapidamente.
Módulos vs. pacotes vs. JARs
Esses três são fáceis de confundir:
| Conceito | O que agrupa | Regra de visibilidade |
|---|---|---|
| Package | classes | public/protected/package-private dentro do JAR |
| JAR | packages + resources | tudo que é public é visível no classpath |
| Module | packages | apenas pacotes exported são visíveis para outros módulos |
Um módulo é normalmente empacotado como um JAR (um "modular JAR" — um JAR comum com um module-info.class na raiz). A diferença é o descritor: coloque o JAR no classpath e as regras são ignoradas; coloque-o no module path e o JPMS as impõe.
Encapsulamento forte, em uma frase
A regra principal: um pacote é invisível para outros módulos, a menos que seu módulo o exports — mesmo que suas classes sejam public. public agora significa "acessível ao código que pode ler este pacote", e ler um pacote exige um exports mais um requires. É por isso que o próprio JDK pôde finalmente ocultar seus internos: java.base exporta java.util mas não jdk.internal.misc.
O JDK também é modular
Desde o Java 9, o JDK está dividido em ~70 módulos (java.base, java.sql, java.xml, java.net.http, …). java.base é especial: é implicitamente requerido por todos os módulos e contém os elementos essenciais da linguagem (java.lang, java.util, java.io). Toda classe que você já usou vive em um desses módulos — o que o exemplo prático abaixo torna visível.
Um exemplo prático: inspecionando módulos em tempo de execução
Você não precisa escrever um módulo para ver módulos: a Module API em tempo de execução reporta o módulo de qualquer classe. Este programa pergunta a várias classes a qual módulo elas pertencem, verifica seu próprio módulo e dá uma olhada na boot layer que a JVM iniciou.
O que extrair da execução:
String,ArrayListeHttpClientreportaramjava.base,java.baseejava.net.http. Toda classe pertence a exatamente um módulo, egetModule()informa qual — os tipos da linguagem vivem todos emjava.base, enquantoHttpClientestá em seu próprio módulo, para o qual você precisaria declararrequires java.net.http.- A própria classe do programa reportou
isNamed() == falsee umgetName()denull. Código executado a partir do classpath cai no unnamed module, um bucket de compatibilidade que não requer nada explicitamente e lê todos os outros módulos. É por isso que programas classpath ainda compilam e executam sem alterações no Java 9+. ModuleLayer.boot()expôs o grafo de módulos que a JVM resolveu na inicialização — contar osjava.*mostra que o JDK realmente está dividido em muitos módulos, não em um monólito.java.basenão é aberto (isOpen() == false), mas exporta muitos pacotes; ele expõejava.langejava.utila todos, enquanto mantémjdk.internal.*oculto. Exportar um pacote e abrir um módulo são opções diferentes — um capítulo posterior retorna aopens.- Nada aqui exigiu um
module-info.java. A Module API é metadado reflexivo disponível para qualquer programa; escrever seu próprio módulo (próximo capítulo) é o que permite a você declarar essas regras em vez de apenas observar as do JDK.
O que o restante desta parte cobre
module-info.java— as diretivas:requires,exports,opens,uses,provides.- Tipos de módulos — módulos nomeados, automáticos e sem nome, e como eles se misturam.
- Services — desacoplando uma interface de sua implementação com
uses/provideseServiceLoader. - Migração — movendo um aplicativo classpath existente para o module path sem uma reescrita completa.
Módulos são opcionais: um aplicativo Java pode rodar para sempre no classpath. Mas entendê-los explica o JDK moderno, desbloqueia runtimes customizados com jlink e oferece às bibliotecas um encapsulamento real. O próximo capítulo, a declaração module-info.java, escreve o descritor que torna um módulo um módulo.