← Java: linguagem e decisões de aplicação
10 / 10 · 45 MIN

Módulos, entrega e diagnóstico

Relaciona APIs, módulos, versões e artefactos para interpretar falhas de compilação e entrega.

Do nome ao acesso permitido

Um import resolve a forma de escrever um nome no código. Não instala um JAR nem concede acesso a um pacote encapsulado. Quando um tipo não fica disponível, separa três perguntas: onde estão os seus bytes, que módulo os contém e que acesso o consumidor tem. Alterar apenas o import pode deixar intacto o problema de entrega ou de encapsulamento.

Considera uma classe public p.Item no módulo inventory.api e um consumidor no módulo orders.app. O consumidor precisa de ler inventory.api, e esse módulo precisa de exportar p para o consumidor. Mesmo cumprindo essas condições, um membro private continua privado. A visibilidade do módulo não substitui a visibilidade dos membros.

A dependência em java.base é implícita para os outros módulos. Isso permite usar os tipos fundamentais sem repetir requires java.base em todos os descritores. Não generalizes essa exceção: uma dependência em java.sql, por exemplo, tem de ser tratada conforme o código e o grafo do módulo. O diagnóstico deve identificar a relação em falta, não abrir todo o sistema.

API, reflexão e dependências transitivas

exports e opens respondem a necessidades diferentes. Uma API exportada permite ao consumidor usar normalmente os tipos públicos acessíveis. Uma abertura permite reflexão em execução, incluindo acesso que não foi publicado como API de compilação. A forma qualificada, como opens orders.model to audit.reader, delimita o destinatário da abertura.

Uma ferramenta que precisa de observar campos privados de um único pacote não justifica, por si só, open module. A decisão deve conservar o limite solicitado. Abrir todos os pacotes torna a permissão mais ampla do que o caso exige; exportar um pacote não concede automaticamente acesso reflexivo profundo aos seus membros privados.

requires transitive trata uma outra relação. Se reports.api expõe tipos de model.api nas suas assinaturas, a dependência transitiva pode tornar model.api legível aos consumidores de reports.api. Não copia classes nem reexporta fisicamente pacotes. Ao desenhar uma API, revê os tipos que atravessam a fronteira para decidir quais dependências fazem parte do contrato dos consumidores.

Compilar para um destino explícito

Regista separadamente a versão do compilador, o destino da compilação e a JVM que vai carregar o resultado. Os três valores podem ser diferentes. Um resultado que funciona no ambiente do autor ainda precisa de corresponder ao destino contratado. Essa correspondência inclui os JARs externos que serão entregues.

Em javac 25, --release 21 seleciona regras da linguagem, formato de classes e APIs da plataforma relativos a Java 21. Usar apenas --target não oferece a mesma restrição sobre as APIs referidas pelo código. Não é suficiente produzir um cabeçalho antigo enquanto se depende de métodos que o destino não possui.

A opção não reescreve bibliotecas já compiladas nem certifica todos os seus requisitos. Se uma dependência exige uma JVM posterior, recompilar só a aplicação não resolve a entrega. Escolhe uma versão compatível, reconstrói a dependência quando viável ou revê o destino por um processo autorizado. Neste percurso, os excertos usam Java SE 25 sem funcionalidades preview; questões sobre destinos anteriores dizem-no explicitamente.

Arranque e conjunto de artefactos

Uma descrição de entrega deve identificar a classe ou módulo de entrada e os artefactos que serão procurados. O nome app.jar, sozinho, não descreve todas as dependências. Mantém uma lista inequívoca das versões e evita misturar cópias antigas e novas com a mesma classe. A investigação de uma falha tem de usar o conjunto efetivamente carregado.

No lançamento por classe, -cp permite indicar diretórios e JARs. O separador é dois pontos em POSIX e ponto e vírgula em Windows. No modo -jar, outras definições de class path são ignoradas: não assumas que acrescentar -cp ao mesmo comando inclui automaticamente uma biblioteca externa. A entrega pode usar dependências declaradas no manifesto, um empacotamento apropriado ou um lançamento por classe com class path completo.

Para módulos, --module-path identifica onde procurar módulos e -m seleciona o módulo e, quando indicado, a classe principal. São conceitos diferentes do nome de pacote. Ao rever um comando, pergunta qual modo está ativo e qual conjunto de ficheiros esse modo realmente considera. Os comandos da avaliação são objetos de interpretação; não é necessário executá-los.

Serviços e implementações descobertas

Uma interface de serviço permite que o consumidor expresse o contrato de que precisa sem nomear cada implementação concreta. ServiceLoader liga esse contrato aos providers disponíveis. A existência da interface não garante que haja um provider instalado, que a sua configuração esteja correta ou que a instanciação termine com sucesso.

Num consumidor modular explícito, usa-se uses para declarar o serviço consumido e uma dependência que torne a interface acessível quando ela reside noutro módulo. O módulo do provider declara provides ... with .... Na entrega por class path, a configuração correspondente reside em META-INF/services. Não confundas esses dois mecanismos nem suponhas que uma classe é descoberta só por implementar a interface.

A descoberta e a instanciação podem ocorrer de forma preguiçosa. Obter o objeto ServiceLoader não prova que todos os providers foram carregados. O consumidor deve tratar ausência e falhas de configuração de forma compatível com o seu requisito. Se a aplicação necessita de um formato específico, ter algum provider não prova que o provider certo está disponível; a decisão deve observar a capacidade necessária.

O erro identifica uma fronteira

UnsupportedClassVersionError aponta para um formato de classe que a JVM não suporta. No caso de uma classe compilada para uma versão posterior, identifica a sua origem e compara o destino dessa classe com a JVM real. Não atribuas automaticamente a falha à aplicação principal: a classe rejeitada pode vir de uma biblioteca.

NoSuchMethodError descreve outra situação. O consumidor contém uma referência a um método que a definição carregada já não oferece. Se o build usou Lib v2 e a entrega carregou Lib v1, acrescentar tentativas não cria o método em falta. A ação útil é reconciliar o contrato binário e os artefactos selecionados, preservando a evidência da origem das classes.

jdeps ajuda a observar dependências das classes inspecionadas. O seu resultado não substitui a configuração que escolhe nomes de plugins em execução nem demonstra que um provider inicia corretamente. Combina essa leitura estática com os dados de entrega e os critérios de aceitação. Uma ausência na análise é uma observação delimitada, não uma prova universal de que um componente nunca é necessário.

Revisão guiada de três descritores

O exemplo apresenta três module-info.java distintos. model.api exporta model; reports.api depende transitivamente de model.api e exporta reports; reports.app depende de reports.api. Assume que os pacotes existem, os tipos expostos são públicos e os três módulos são resolvidos normalmente. Não se trata de três declarações a guardar no mesmo ficheiro.

Segue o grafo antes de decidir: reports.app lê reports.api e, pela dependência transitiva, lê model.api. Como model está exportado, o consumidor pode usar um tipo público desse pacote. Se reports.api trocasse requires transitive por requires simples, essa leitura indireta deixaria de ser garantida. Isso é relevante quando a assinatura pública de um relatório devolve um tipo do modelo.

Este raciocínio não estabelece a presença dos JARs no destino nem a compatibilidade da JVM. A revisão de entrega ainda precisa de relacionar os descritores, os bytes selecionados e o runtime. Na avaliação final, escolhe a consequência demonstrada pelos dados e conserva os limites da conclusão. Nenhuma resposta exige compilar os descritores ou implementar um plugin.

// Three separate module-info.java files; illustrative descriptors.
// model.api/module-info.java
module model.api {
    exports model;
}

// reports.api/module-info.java
module reports.api {
    requires transitive model.api;
    exports reports;
}

// reports.app/module-info.java
module reports.app {
    requires reports.api;
}
NA PRÁTICA

reports.app lê model.api através da dependência transitiva de reports.api. O pacote model exportado torna os seus tipos públicos acessíveis; membros privados mantêm as suas restrições.

Armadilhas comuns

Confundir import com instalação; abrir todos os pacotes; usar --target como garantia das APIs; ignorar dependências já compiladas; tratar análise estática como prova de execução.

Tópicos relacionados: Objetos, interfaces e records · Exceções, recursos e ficheiros · Concorrência, estado e cancelamento

Leva esta ideia contigo

Uma entrega coerente liga nomes, permissões, versões e bytes reais ao destino definido.

Criar conta

Referência: JLS 25: Packages and modules · Java SE 25; no preview features