Introdução ao Projeto Jigsaw
1. Introdução
Project Jigsaw é um projeto abrangente com os novos recursos voltados para dois aspectos:
-
a introdução do sistema de módulos na linguagem Java
-
e sua Implementação na Origem JDK e Java Runtime
Neste artigo, vamos apresentar o projeto Jigsaw e seus recursos e, finalmente, encerrá-lo com um aplicativo modular simples.
2. Modularidade
Simplificando, modularidade é um princípio de design que nos ajuda a alcançar:
-
acoplamento frouxo entre componentes
-
contratos claros e dependências entre componentes
-
implementação oculta usando encapsulamento forte
2.1. Unidade de Modularidade
Agora vem a pergunta sobre qual é a unidade de modularidade? No mundo Java, especialmente com OSGi, os JARs foram considerados como a unidade de modularidade.
Os JARs ajudaram a agrupar os componentes relacionados, mas eles têm algumas limitações:
-
contratos e dependências explícitos entre JARs
-
encapsulamento fraco de elementos dentro dos JARs
2.2. JAR Hell
Havia outro problema com os JARs - o inferno dos JARs. Várias versões dos JARs no caminho de classe resultaram emClassLoader carregando a primeira classe encontrada do JAR, com resultados muito inesperados.
O outro problema com o JVM usando o classpath era que a compilação do aplicativo seria bem-sucedida, mas o aplicativo falhará no tempo de execução comClassNotFoundException, devido aos JARs ausentes no classpath no tempo de execução.
2.3. Nova Unidade de Modularidade
Com todas essas limitações, ao usar o JAR como unidade de modularidade, os criadores da linguagem Java criaram uma nova construção na linguagem chamada módulos. E com isso, há um sistema modular totalmente novo planejado para Java.
3. Projeto Jigsaw
As principais motivações para este projeto são:
-
create a module system for the language - implementado emJEP 261
-
apply it to the JDK source - implementado emJEP 201
-
modularize the JDKlibraries - implementado emJEP 200
-
update the runtime to support modularity - implementado emJEP 220
-
be able to create smaller runtime with a subset of modules from JDK - implementado emJEP 282
Outra iniciativa importante é encapsular as APIs internas no JDK, aquelas que estão nos pacotessun.* e outras APIs não padrão. Essas APIs nunca foram projetadas para serem usadas pelo público e nunca foram planejadas para serem mantidas. Mas o poder dessas APIs fez com que os desenvolvedores Java os alavancassem no desenvolvimento de diferentes bibliotecas, estruturas e ferramentas. Foram fornecidas substituições para poucas APIs internas e as outras foram movidas para módulos internos.
4. Novas ferramentas para modularidade
-
jdeps - ajuda a analisar a base de código para identificar as dependências de APIs JDK e JARs de terceiros. Ele também menciona o nome do módulo em que a API JDK pode ser encontrada. Isso facilita a modularização da base de código
-
jdeprscan - ajuda a analisar a base de código para uso de quaisquer APIs obsoletas
-
jlink - ajuda na criação de um tempo de execução menor, combinando os módulos do aplicativo e do JDK
-
jmod - ajuda a trabalhar com arquivos jmod. O jmod é um novo formato para empacotar os módulos. Esse formato permite incluir código nativo, arquivos de configuração e outros dados que não cabem nos arquivos JAR
5. Arquitetura do sistema do módulo
O sistema de módulos, implementado na linguagem, os suporta como uma construção de nível superior, assim como os pacotes. Os desenvolvedores podem organizar seu código em módulos e declarar dependências entre eles em seus respectivos arquivos de definição de módulo.
Um arquivo de definição de módulo, denominadomodule-info.java, contém:
-
seu nome
-
os pacotes que disponibiliza publicamente
-
os módulos dos quais depende
-
quaisquer serviços que consome
-
qualquer implementação para o serviço que fornece
Os dois últimos itens da lista acima não são comumente usados. Eles são usados apenas quando os serviços estão sendo fornecidos e consumidos por meio da interfacejava.util.ServiceLoader.
Uma estrutura geral do módulo é semelhante a:
src
|----com.example.reader
| |----module-info.java
| |----com
| |----example
| |----reader
| |----Test.java
|----com.example.writer
|----module-info.java
|----com
|----example
|----writer
|----AnotherTest.java
A ilustração acima define dois módulos:com.example.readerecom.example.writer. Cada um deles tem sua definição especificada emmodule-info.javae os arquivos de código colocados emcom/example/readerecom/example/writer, respectivamente.
5.1. Terminologias de definição de módulo
Vejamos algumas das terminologias; usaremos ao definir o módulo (ou seja, dentro demodule-info.java):
-
module: o arquivo de definição do módulo começa com esta palavra-chave seguida por seu nome e definição
-
requires: é usado para indicar os módulos dos quais depende; um nome de módulo deve ser especificado após esta palavra-chave
-
transitive: é especificado após a palavra-chaverequires; isso significa que qualquer módulo que dependa do módulo que definerequires transitive <modulename> obtém uma dependência implícita de <modulename>
-
exports: é usado para indicar os pacotes dentro do módulo disponíveis publicamente; um nome de pacote deve ser especificado após esta palavra-chave
-
opens: é usado para indicar os pacotes que são acessíveis apenas em tempo de execução e também disponíveis para introspecção via APIs de reflexão; isso é bastante significativo para bibliotecas como Spring e Hibernate, que dependem muito das APIs do Reflection; opens também pode ser usado no nível do módulo, caso em que todo o módulo está acessível em tempo de execução
-
uses: é usado para indicar a interface de serviço que este módulo está usando; um nome de tipo, ou seja, nome completo de classe / interface, deve ser especificado após esta palavra-chave
-
provides … with .. .: são usados para indicar que fornece implementações, identificadas após a palavra-chavewith, para a interface de serviço identificada após a palavra-chaveprovides
6. Aplicação Modular Simples
Vamos criar um aplicativo modular simples com módulos e suas dependências, conforme indicado no diagrama abaixo:
Ocom.example.student.model é o módulo raiz. Ele define a classe de modelocom.example.student.model.Student, que contém as seguintes propriedades:
public class Student {
private String registrationId;
//other relevant fields, getters and setters
}
Ele fornece outros módulos com tipos definidos no pacotecom.example.student.model. Isso é feito definindo-o no arquivomodule-info.java:
module com.example.student.model {
exports com.example.student.model;
}
O módulocom.example.student.service fornece uma interfacecom.example.student.service.StudentService com operações CRUD abstratas:
public interface StudentService {
public String create(Student student);
public Student read(String registrationId);
public Student update(Student student);
public String delete(String registrationId);
}
Depende do módulocom.example.student.model e disponibiliza os tipos definidos no pacotecom.example.student.service para outros módulos:
module com.example.student.service {
requires transitive com.example.student.model;
exports com.example.student.service;
}
Fornecemos outro módulocom.example.student.service.dbimpl, que fornece a implementaçãocom.example.student.service.dbimpl.StudentDbService para o módulo acima:
public class StudentDbService implements StudentService {
public String create(Student student) {
// Creating student in DB
return student.getRegistrationId();
}
public Student read(String registrationId) {
// Reading student from DB
return new Student();
}
public Student update(Student student) {
// Updating sutdent in DB
return student;
}
public String delete(String registrationId) {
// Deleting sutdent in DB
return registrationId;
}
}
Depende diretamente decom.example.student.servicee transitivamente decom.example.student.modele sua definição será:
module com.example.student.service.dbimpl {
requires transitive com.example.student.service;
requires java.logging;
exports com.example.student.service.dbimpl;
}
O módulo final é um módulo cliente - que aproveita o módulo de implementação de serviçocom.example.student.service.dbimpl para realizar suas operações:
public class StudentClient {
public static void main(String[] args) {
StudentService service = new StudentDbService();
service.create(new Student());
service.read("17SS0001");
service.update(new Student());
service.delete("17SS0001");
}
}
E sua definição é:
module com.example.student.client {
requires com.example.student.service.dbimpl;
}
7. Compilando e executando a amostra
Fornecemos scripts para compilar e executar os módulos acima para as plataformas Windows e Unix. Eles podem ser encontrados no projetocore-java-9here. A ordem de execução para a plataforma Windows é:
-
compilar-modelo-aluno
-
compilar-serviço-aluno
-
compilar-aluno-serviço-dbimpl
-
compilar-aluno-cliente
-
executar-aluno-cliente
A ordem de execução da plataforma Linux é bastante simples:
-
módulos de compilação
-
executar-aluno-cliente
Nos scripts acima, você será apresentado aos dois argumentos de linha de comando a seguir:
-
–Module-source-path
-
–Module-path
O Java 9 está eliminando o conceito de caminho de classe e, em vez disso, apresenta o caminho do módulo. Este caminho é o local onde os módulos podem ser descobertos.
Podemos definir isso usando o argumento da linha de comando:–module-path.
Para compilar vários módulos de uma vez, usamos–module-source-path. Este argumento é usado para fornecer o local para o código-fonte do módulo.
8. Sistema de módulo aplicado à fonte JDK
Cada instalação do JDK é fornecida com umsrc.zip. Este arquivo contém a base de código para as APIs JDK Java. Se você extrair o arquivo, encontrará várias pastas, algumas começando comjava, poucas comjavafxe o restante comjdk. Cada pasta representa um módulo.
Os módulos que começam comjava são os módulos JDK, aqueles que começam comjavafx são os módulos JavaFX e outros que começam comjdk são os módulos de ferramentas JDK.
Todos os módulos JDK e todos os módulos definidos pelo usuário dependem implicitamente do módulojava.base. O módulojava.base contém APIs JDK comumente usadas como Utils, Collections, IO, Concurrency entre outros. O gráfico de dependência dos módulos JDK é:
Você também pode consultar as definições dos módulos JDK para ter uma ideia da sintaxe para defini-los emmodule-info.java.
9. Conclusão
Neste artigo, analisamos a criação, compilação e execução de um aplicativo modular simples. Também vimos como o código-fonte JDK havia sido modularizado.
Existem alguns recursos mais interessantes, como criar um tempo de execução menor usando a ferramenta vinculador - jlink e criar jars modulares entre outros recursos. Vamos apresentar esses recursos em detalhes em artigos futuros.
O Project Jigsaw é uma grande mudança, e teremos que esperar e observar como ele é aceito pelo ecossistema do desenvolvedor, em particular com as ferramentas e os criadores da biblioteca.
O código usado neste artigo pode ser encontradoover on GitHub.