Módulos - alandrade21/docsCompartilhados GitHub Wiki
Nesta página:
A estrutura de um projeto modular é diferente:
src
|__ hello
|__ br.com.pacote
| |__ HelloModules.java
|__ module-info.java
Há um novo nível entre a pasta src
e os pacotes. Esse novo nível é a pasta onde fica o arquivo module-info.java
de declaração do módulo.
O módulo deve ter o mesmo nome da pasta do módulo. Assim, no exemplo acima, a declaração do módulo ficaria:
module hello {
}
Na prática, numa IDE, a pasta de código fonte deve ser configurada para src/hello
(seguindo o exemplo acima), e a pasta de output também deve replicar essa estrutura, ou seja, ela ficaria bin/hello
.
No caso de outros módulos nesse mesmo projeto, cada um teria sua pasta de fonte e de output separadas.
Agora imagine uma situação onde um módulo cliente
precise consumir um módulo api
, que precisa consumir um módulo domínio
.
cliente ---> api ---> domínio
A definição dos módulos fica:
module cliente {
requires br.com.api;
}
module api {
exports br.com.api;
requires br.com.dominio;
}
module dominio {
exports br.com.dominio;
}
Dependências necessárias apenas para compilação (como anotações, por exemplo) podem ser expressas com requires static
.
module framework {
requires static lombok;
}
Usando o mesmo exemplo da seção anterior, imagine agora que no módulo api
há uma classe chamada Serviço
que publique o método public Retorno fazAlgumaCoisa()
, e que a classe Retorno
está definida no módulo dominio
, de forma que o código no módulo cliente chame algo do tipo Retorno retorno = servico.fazAlgumaCoisa();
. Essa chamada retornará o seguinte erro: Retorno is defined in an inaccessible class or interface
.
Há duas soluções para esse problema. A primeira, menos elegante, é colocar a dependência ao módulo dominio
no módulo cliente:
module cliente {
requires br.com.api;
requires br.com.dominio;
}
Essa solução é pouco elegante porque transforma o grafo de dependências de:
cliente ---> api ---> domínio
Em:
cliente ---> api
| |
| |
| v
--> dominio
A melhor solução seria transformar a dependência do módulo api
ao módulo dominio
numa dependência transitiva:
module api {
exports br.com.api;
requires transitive br.com.dominio;
}
Esse mesmo raciocínio deve ser utilizado para tipos utilizados em atributos e tipos de exceções lançadas pelos serviços de um módulo que residam em outros módulos.
Se a compilação for feita com a opção -Xlint:exports
, o compilador gerará um warning para todos os requires
que deveriam ser transitivos e não são.
Imagine um projeto de uma biblioteca que possui vários módulos pouco relacionados entre si, de forma que um consumidor possa utilizar apenas alguns módulos e ignorar outros.
Para evitar que um consumidor tenha que ficar declarando módulos específicos da lib, o que pode ser confuso, é possível declarar um módulo agregador que possua apenas as dependências transitivas para todos os demais módulos da lib, de forma que o cliente use apenas este módulo agregador. Na prática, esse módulo agregador funciona como uma fachada para sua lib.
Exemplo desse módulo agregador:
module library {
requires transitive library.one;
requires transitive library.two;
requires transitive library.three;
}
Ainda seria possível para um cliente usar apenas um módulo específico.
O uso dessa solução é particularmente útil quando é necessário quebrar um módulo que ficou muito grande em módulos menores, mantendo a retrocompatibilidade.
Imagine que seu módulo cliente
precisa consumir serviços de provedores desconhecidos. Então uma interface para esses serviços é criada num módulo api
.
Usemos a interface hipotética abaixo:
package br.eti.alandrade21.api;
...
public interface Servico {
String getName();
double execute(...);
}
O módulo api
ficaria:
module api {
exports br.eti.alandrade21.api;
}
Enquanto o módulo client
ficaria:
module client {
requires br.eti.alandrade21.api;
}
Um primeiro provedor desse serviço, implementando-o em um módulo servicoX
, utilizando uma classe concreta br.com.provedor1.ServicoImpl
(que implementa a interface br.eti.alandrade21.api.Servico
), teria seu módulo definido da seguinte forma:
module servicoX {
requires br.eti.alandrade21.api;
provides br.eti.alandrade21.api.Servico
with br.com.provedor1.ServicoImpl;
}
A cláusula provides
informa que esse módulo fornece uma implementação para a interface Serviço
através da classe ServicoImpl
. Repare que essa classe concreta não é exportada pelo módulo, ficando encapsulada.
Para o cliente consumir este serviço é necessário alterar seu módulo da seguinte forma:
module client {
requires br.eti.alandrade21.api;
uses br.eti.alandrade21.api.Servico;
}
A cláusula uses
informa que este módulo deseja consumir classes que implementem a interface br.eti.alandrade21.api.Servico
como serviços.
Para atender essa necessidade, o sistema de módulos do java implementa um ServiceLoader
.
O client
precisa inserir em seu código o seguinte bloco:
Iterable<Servico> servicos = ServiceLoader.load(Servico.class);
for (Servico servico: servicos) {
System.out.println(servico.getName() + ": " + servico.execute(...));
}
ServiceLoader.load(Servico.class)
busca, no module path por todos os provedores do serviço br.eti.alandrade21.api.Servico
, os instancia e retorna um Iterable
com todas as instâncias criadas.
Dessa forma, as classes concretas que implementam os serviços não precisam estar presentes em tempo de compilação do cliente. É importante perceber que em tempo de execução pode não haver nenhum implementação disponível.
Para que a classe que implementa a interface seja instanciada pelo service loader, essa classe deve ter um construtor padrão público, ou deve prover um método estático de instanciamento, chamado provider()
, e cujo tipo de retorno é o tipo da classe concreta que implementa a interface. No caso do exemplo acima, esse método provider para a classe ServicoImpl
ficaria:
public class ServicoImpl implements Servico {
...
public static ServicoImpl provider() {
return new ServicoImpl();
}
private ServicoImpl(){};
...
}
Por fim, é recomendável colocar o ServiceLoader.load(Servico.class)
dentro da interface, assim:
package br.eti.alandrade21.api;
...
public interface Servico {
String getName();
double execute(...);
static Iterable<Analyzer> getServices() {
return ServiceLoader.load(Analyzer.class);
}
}
Isso faz com que o cliente não tenha que saber nada sobre service loaders. Agora, para obter a lista dos impl é só chamar Servico.getServices()
.
O módulo api precisa ser alterado da seguinte forma:
O módulo api
ficaria:
module api {
exports br.eti.alandrade21.api;
uses br.eti.alandrade21.api.Servico;
}
Obviamente a cláusula uses
sai do módulo Cliente
.
module framework {
requires static fastjsonlib;
uses javamodularity.fastjsonlib.FastJson;
}
FastJson fastJson =
ServiceLoader.load(FastJson.class)
.findFirst() // Retorna um empty Optional se não achar
.orElse(getFallBack());