W3docs

Java Modules: Serviços

Padrão service-loader no sistema de módulos Java usando as diretivas uses e provides.

Um objetivo central dos módulos é o desacoplamento: o código que usa uma capacidade não deve nomear a classe que a implementa. O Java Platform Module System (JPMS) transforma isso em um recurso de primeira classe com duas diretivas — uses e provides — conectadas em tempo de execução pelo ServiceLoader. Este é o substituto modular da antiga convenção de classpath de colocar um arquivo META-INF/services em um JAR.

Este capítulo pressupõe que você já sabe como escrever um descritor module-info.java; aqui adicionamos as diretivas de serviço a ele.

Os três papéis

Um serviço consiste em três partes, idealmente em três módulos diferentes:

  1. A interface de serviço (ou classe abstrata) — o contrato, por exemplo PricingRule. Ela reside em um módulo que a exporta.
  2. O consumidor — código que solicita implementações. Seu módulo declara uses com.acme.PricingRule; e chama ServiceLoader.load(PricingRule.class).
  3. Um ou mais provedores — módulos de implementação. Cada um declara provides com.acme.PricingRule with com.acme.impl.StandardPricing;.

O consumidor nunca importa StandardPricing. Ele conhece apenas a interface. Adicione um novo módulo provedor ao caminho de módulo e o consumidor o detecta automaticamente — sem recompilação, sem alteração de código.

As diretivas em module-info.java

// module com.acme.api
module com.acme.api {
    exports com.acme;            // export the PricingRule interface
}

// module com.acme.app (the consumer)
module com.acme.app {
    requires com.acme.api;
    uses com.acme.PricingRule;   // "I will ServiceLoader.load this"
}

// module com.acme.standard (a provider)
module com.acme.standard {
    requires com.acme.api;
    provides com.acme.PricingRule
        with com.acme.standard.StandardPricing;
}

uses informa ao resolvedor que o consumidor vai consultar aquele serviço, portanto o módulo tem permissão para chamar ServiceLoader.load. provides … with … registra uma implementação. A classe provedora deve ter um construtor público sem argumentos ou um método estático público provider() que retorna uma instância.

Consumindo com ServiceLoader

ServiceLoader<PricingRule> loader = ServiceLoader.load(PricingRule.class);
for (PricingRule rule : loader) {
    System.out.println(rule.describe());
}
// or pick the first available
PricingRule rule = ServiceLoader.load(PricingRule.class)
    .findFirst()
    .orElseThrow();

ServiceLoader é lazy — cada provedor é instanciado somente quando o iterador o alcança — e armazena instâncias em cache. Ele implementa Iterable, portanto um loop for-each percorre todos os provedores registrados.

Um exemplo prático: ServiceLoader sobre um serviço real do JDK

O JDK já inclui um serviço que você pode carregar sem precisar construir três módulos: java.util.spi.ToolProvider. O compilador (javac), a ferramenta JAR (jar) e outros estão registrados como provedores dessa interface dentro dos módulos JDK. Este programa os carrega por meio do ServiceLoader — exatamente o código do consumidor acima, aplicado a um serviço que já está configurado.

java— editable, runs on the server

O que extrair da execução:

  • O loop for-each percorreu todos os ToolProvider que o runtime registrou (tipicamente javac, jar, javadoc, …) sem que o programa importasse nenhuma dessas classes de implementação. Esse é exatamente o ponto dos serviços: o consumidor depende de ToolProvider, a interface, e descobre provedores concretos em tempo de execução.
  • Cada provedor exibiu um nome de classe de implementação diferente, embora compartilhem uma única interface. Os módulos JDK declararam provides java.util.spi.ToolProvider with … em seus descritores; o ServiceLoader os coletou todos. Adicionar outro módulo provedor o faria aparecer neste mesmo loop sem nenhuma alteração aqui.
  • ToolProvider.findFirst("javac") retornou um Optional e o código tratou ambos os ramos. Consultas a serviços são inerentemente "pode estar ausente" — um runtime mínimo poderia não incluir nenhum provedor de ferramentas — portanto a API obriga você a planejar para o caso vazio em vez de presumir que existe uma implementação.
  • Executar javac --version por meio do provedor carregado prova que o objeto é totalmente funcional, alcançado puramente pelo contrato de serviço. O consumidor invocou comportamento real sem uma dependência em tempo de compilação nas classes do compilador.
  • ServiceLoader instancia de forma lazy e apenas o que você itera; em uma configuração real de três módulos, o module-info do consumidor precisaria de uses java.util.spi.ToolProvider; para que a chamada seja permitida. Os próprios módulos do JDK já o declaram, por isso isso funciona sem alterações.

Por que usar serviços

  • Arquiteturas de plugins — coloque um JAR provedor no caminho de módulo para estender uma aplicação.
  • Implementações opcionais — escolha um driver SSL, de log ou de banco de dados em tempo de execução conforme o módulo provedor presente.
  • Inversão de dependência — o módulo consumidor de alto nível depende de um módulo de interface, nunca dos provedores de baixo nível, portanto as setas de dependência apontam para o contrato estável.

Esse é o mesmo mecanismo que o JDK usa para DriverManager, provedores de charset e as cronologias de java.time. O capítulo final desta parte, Migrando para Java Modules, une tudo: como mover uma aplicação de classpath existente para o caminho de módulo passo a passo.

Prática

Prática
Um módulo consumidor chama 'ServiceLoader.load(PaymentGateway.class)', mas o loop não encontra nenhum provedor em tempo de execução, mesmo que um módulo provedor declarando 'provides PaymentGateway with StripeGateway' esteja no caminho de módulo. O módulo consumidor compila e inicia corretamente. Qual é a causa mais provável?
Um módulo consumidor chama 'ServiceLoader.load(PaymentGateway.class)', mas o loop não encontra nenhum provedor em tempo de execução, mesmo que um módulo provedor declarando 'provides PaymentGateway with StripeGateway' esteja no caminho de módulo. O módulo consumidor compila e inicia corretamente. Qual é a causa mais provável?
Was this page helpful?