Introdução ao Projeto Jigsaw

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:

image

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 é:

  1. compilar-modelo-aluno

  2. compilar-serviço-aluno

  3. compilar-aluno-serviço-dbimpl

  4. compilar-aluno-cliente

  5. executar-aluno-cliente

A ordem de execução da plataforma Linux é bastante simples:

  1. módulos de compilação

  2. 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.

image

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 é:

image

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.