W3docs

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 public em 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 NoClassDefFoundError explodí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:

ConceitoO que agrupaRegra de visibilidade
Packageclassespublic/protected/package-private dentro do JAR
JARpackages + resourcestudo que é public é visível no classpath
Modulepackagesapenas 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.

java— editable, runs on the server

O que extrair da execução:

  • String, ArrayList e HttpClient reportaram java.base, java.base e java.net.http. Toda classe pertence a exatamente um módulo, e getModule() informa qual — os tipos da linguagem vivem todos em java.base, enquanto HttpClient está em seu próprio módulo, para o qual você precisaria declarar requires java.net.http.
  • A própria classe do programa reportou isNamed() == false e um getName() de null. 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 os java.* mostra que o JDK realmente está dividido em muitos módulos, não em um monólito.
  • java.base não é aberto (isOpen() == false), mas exporta muitos pacotes; ele expõe java.lang e java.util a todos, enquanto mantém jdk.internal.* oculto. Exportar um pacote e abrir um módulo são opções diferentes — um capítulo posterior retorna a opens.
  • 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/provides e ServiceLoader.
  • 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.

Prática

Prática
Um JAR de biblioteca contém uma classe 'public' no pacote 'com.acme.internal' que os autores pretendem usar apenas dentro da biblioteca. O JAR é construído como um modular JAR cujo 'module-info.java' exporta 'com.acme.api' mas não 'com.acme.internal'. O que acontece quando esse JAR é colocado no MODULE PATH e outro módulo tenta importar 'com.acme.internal.Helper'?
Um JAR de biblioteca contém uma classe 'public' no pacote 'com.acme.internal' que os autores pretendem usar apenas dentro da biblioteca. O JAR é construído como um modular JAR cujo 'module-info.java' exporta 'com.acme.api' mas não 'com.acme.internal'. O que acontece quando esse JAR é colocado no MODULE PATH e outro módulo tenta importar 'com.acme.internal.Helper'?
Was this page helpful?