W3docs

Migrando para Módulos Java

Estratégias para migrar aplicações Java baseadas em classpath para o Java Platform Module System.

Você não precisa modularizar para migrar para Java 9+. Uma aplicação em classpath roda sem alterações — tudo se torna um único unnamed module. A migração é uma etapa opcional que você realiza quando deseja encapsulamento forte, um runtime customizado com jlink, ou um grafo de dependências mais limpo. A arte está em fazê-la de forma incremental, porque os três tipos de módulos — named, automatic e unnamed — permitem que código modular e não modular coexistam.

Este capítulo aborda quando a migração vale a pena, como fazer uma aplicação existente rodar em uma JDK moderna primeiro, as duas direções possíveis de modularização (bottom-up vs. top-down), as ferramentas que fazem o trabalho pesado, e um exemplo executável que inspeciona o grafo de módulos da mesma forma que o jdeps faz. Se os termos aqui são desconhecidos, comece com a introdução a módulos e declarando um módulo.

Primeiro: apenas rode no Java 9+ (sem módulos)

Antes de adicionar qualquer module-info.java, recompile e execute sua aplicação existente na nova JDK com tudo no classpath. As prováveis falhas não têm nada a ver com seus módulos e sim com a JDK agora modular:

  • Módulos Java EE removidosjava.xml.bind (JAXB), java.activation, CORBA, etc. foram removidos da JDK. Adicione-os de volta como dependências comuns.
  • Internos encapsuladossun.misc.Unsafe e similares não são mais acessíveis. As flags --add-exports / --add-opens são uma saída de emergência enquanto você remove o uso deles.
  • Pacotes divididos — dois JARs contribuindo com o mesmo pacote agora entram em conflito no module path (mas não no classpath).

Deixe a aplicação funcionando no classpath primeiro. Somente então comece a modularizar.

Migração bottom-up

A estratégia preferida quando você controla toda a base de código:

  1. Comece pelas bibliotecas folha — módulos que não dependem de nada seu.
  2. Dê a cada uma um module-info.java e mova-a para o module path.
  3. Os dependentes delas podem agora usar requires para referenciá-las. Trabalhe subindo a árvore de dependências em direção ao ponto de entrada da aplicação.

Isso funciona porque um named module pode requerer outros named modules e módulos automatic — portanto, desde que as próprias dependências de uma folha sejam pelo menos automáticas, você pode modularizá-la. Cada passo mantém o build funcionando. Se uma folha expõe comportamento plugável, esse também é um ponto natural para introduzir uma fronteira de serviços para que seus dependentes usem uma interface em vez de uma classe concreta.

Migração top-down

Quando você não controla as bibliotecas (JARs de terceiros sem descritores), faça o caminho inverso:

  1. Modularize primeiro seu próprio código de nível superior.
  2. Coloque os JARs de terceiros ainda não modulares no module path, onde se tornam automatic modules.
  3. Use requires referenciando-os pelo nome automático (derivado do nome do arquivo JAR ou do Automatic-Module-Name).
  4. À medida que cada biblioteca disponibiliza um module-info.java real, substitua a dependência automática pela nomeada — sem alteração no seu requires.

Os automatic modules são o andaime que torna a migração top-down possível; eles permitem que seu código named dependa de JARs que ainda não são modulares.

Ferramentas e armadilhas

  • jdeps analisa as dependências reais de um JAR e pode até gerar um rascunho inicial de module-info.java (jdeps --generate-module-info). Comece por aí em vez de escrever diretivas à mão.
  • Automatic-Module-Name — se você publica uma biblioteca, adicione esta entrada de manifesto antes de escrever um descritor completo. Ela fixa um nome de módulo estável para que usuários de módulos automáticos downstream não sejam quebrados quando você renomear o JAR posteriormente.
  • Pacotes divididos devem ser mesclados. O module path proíbe dois módulos de possuírem o mesmo pacote; o classpath tolerava isso. Este é o bloqueador de migração mais comum.
  • opens para reflexão. Frameworks que refletem sobre suas classes (Jackson, JPA, Spring) precisam de opens, ou você obterá InaccessibleObjectException em tempo de execução mesmo que a compilação tenha passado.
Aviso

Pacotes divididos são a falha que mais surpreende as pessoas. No classpath, dois JARs contendo classes em com.example.util simplesmente se mesclavam; no module path o resolver os rejeita com um erro "module reads package ... from both". Não existe flag que torne isso legal — você deve mesclar o pacote duplicado em um único módulo (ou renomear um deles). Audite pacotes divididos com jdeps antes de começar a escrever descritores.

Um exemplo prático: inspecionando um grafo de dependências como o jdeps

O planejamento de migração começa com "o que depende do quê." Este programa lê os descritores de módulos da camada boot em tempo de execução — a mesma informação de requires que o jdeps apresenta — e também detecta se ele próprio está rodando como um named module ou no classpath, exatamente a verificação que indica até onde uma migração progrediu.

java— editable, runs on the server

O que observar na execução:

  • O programa reportou que estava rodando como módulo UNNAMED — exatamente o que se espera de código em classpath, e o sinal em tempo de execução de que esta base de código não foi modularizada ainda. Executar novamente após adicionar um module-info e mover para o module path mudaria isso para NAMED, fornecendo uma verificação concreta de "já chegamos lá?" durante a migração.
  • inspect("java.sql") exibiu sua lista real de requires, incluindo uma dependência transitiva do estilo java.transaction.xa ou java.logging. Essa é a mesma informação que o jdeps apresenta — conhecer as dependências reais de um módulo é o primeiro passo para escrever seu module-info.java, e o descritor já as contém.
  • Alguns requires exibiram (transitive). Essas são as dependências que um módulo re-exporta; quando você usa requires java.sql, você lê automaticamente suas dependências transitivas também. Identificá-las indica quais linhas requires transitive copiar ao modularizar código que envolve tal módulo.
  • findModule retornou um Optional, reforçando que um módulo pode simplesmente não estar presente em um dado runtime — uma imagem compactada com jlink pode omitir java.desktop completamente. Os planos de migração devem considerar quais módulos realmente são entregues no runtime de destino.
  • Todo módulo depende fundamentalmente de java.base, e ele nunca aparece em uma lista de requires porque é implícito. A contagem total de módulos boot mostra que a JDK é um grafo que pode ser consultado programaticamente — a fundação sobre a qual ferramentas como jdeps e jlink constroem para analisar e reduzir uma aplicação.

Uma ordem de migração sensata, resumida

  1. Execute no classpath na nova JDK; corrija falhas de módulos removidos e APIs internas.
  2. Use jdeps para mapear dependências e encontrar pacotes divididos.
  3. Adicione Automatic-Module-Name a quaisquer bibliotecas que você publique.
  4. Modularize bottom-up se você controla a árvore, top-down (apoiando-se em automatic modules) se não controla.
  5. Adicione opens onde frameworks refletem; verifique em tempo de execução, não apenas em tempo de compilação.

Isso conclui a parte sobre o Java Platform Module System: agora você sabe o que são módulos, como declarar um, os três tipos e como coexistem, como serviços desacoplam módulos, e como migrar uma aplicação existente para o module path sem uma reescrita.

Prática

Prática
Sua equipe possui toda a árvore de código-fonte de uma aplicação e quer encapsulamento forte em toda ela. A biblioteca A não depende de nada seu; a biblioteca B depende de A; o executável depende de B. Qual ordem de migração mantém cada build intermediário funcionando com o menor número de workarounds com automatic modules?
Sua equipe possui toda a árvore de código-fonte de uma aplicação e quer encapsulamento forte em toda ela. A biblioteca A não depende de nada seu; a biblioteca B depende de A; o executável depende de B. Qual ordem de migração mantém cada build intermediário funcionando com o menor número de workarounds com automatic modules?
Was this page helpful?