Modularidade com Java 9 – Parte 2

No texto anterior foi apresentado a problemática da visibilidade e acessibilidade dos tipos e como elas afetam a modularização em vários níveis, desde da plataforma até bibliotecas e como o JDK 9 pretende resolver este problema por meio da forte encapsulação do sistema de módulos. Agora, vamos verificar como esta solução é aplicada aplicações no Java 9.

Módulo de banco de dados

Vamos imaginar, para fins didáticos, que estamos desejando criar um módulo com funções auxiliares de conectividade com banco de dados, algo semelhante ao projeto do Spring JDBC, sendo assim, vamos criar uma interface que utiliza como parâmetro uma instância de java.sql.ResultSet:

package br.com.esig.database;

import java.sql.ResultSet;

public interface MapeadorLinha<T> {
    T mapear(ResultSet res, int index);
}

Declaração de módulos

Os arquitetos da linguagem decidiram definir o módulo como um elemento da linguagem, semlhante ao que fazemos com classes e interfaces. O arquivo de declaração do módulo é, por convenção, definido em um arquivo chamado module-info.java e deve ficar na raiz dos pacotes. Os nomes dos módulos, com os pacotes, não devem conflitar. É recomendado a nomeclatura do módulo como sendo o nome reverso do domínio da organização. Um declaração de módulo simples seria como a seguinte:

// module-info.java
module br.com.esig.database {
}

Com isso, ao tentar compilar o nosso projeto encontramos a seguinte mensagem de erro:

// MapeadorLinha.java: error: package java.sql is not visible
// import java.sql.ResultSet;
//  (package java.sql is declared in module java.sql, but module 
// br.com.esig.database does not read it)
// 1 error

A mensagem de erro acima, apresentada no momento da compilação, mostra que a classe java.sql.ResultSet não é reconhecida pelo compilador! Isto ocorre pois, a partir do JDK 9, quando incluimos um arquivo module-info.java os únicos tipos acessíveis para utilização são exportados pelo módulo java.base, que contém tipos definidos nos pacote java.lang, java.util, java.nio, java.timee alguns outros.

Para que nosso código funcione, temos de definir quais módulos nossa aplicação terá acesso (visibilidade) e quais tipos nosso pacote irá permitir acesso (acessibilidade). Uma segunda versão da nossa definição de módulos seria a seguinte:

module br.com.esig.database {
    requires java.sql;
    exports br.com.esig.database.api; 
}

Um módulo é definido pela palavra chave module seguido do nome do módulo, nesse caso, br.com.esig.database. Logo em seguida, no corpo do módulo, solicitamos o acesso ao módulo java.sql por meio da palavra chave requiresjava.sql. Estamos dizendo que o módulo br.com.esig.database irá visualizar os tipos exportados pelo módulo java.sql ou, de forma equivalente, o módulo java.sql é acessível pelo br.com.esig.database.

Ao informar que nosso módulo requiresjava.sql estamos informando ao JDK que, desejamos possuir acesso à todas as classes, interfaces, recursos estáticos que são exportados pelo módulo. Com isso, conseguimos encontrar os tipos java.sql.ResultSet, java.sql.Connection, java.sql.PrepareStatement e outros!

Isso nos leva à segunda parte do código, exportsbr.com.esig.database.api está informando que, quem utilizar o módulo br.com.esig.database (Por meio de um requires) terá acesso aos tipos públicos presentes no pacote br.com.esig.database.api.

Grafo de módulos

A relação entre os módulos do Java é representado por um grafo, onde o sentido da seta indica quem lê um módulo. A estrutura modular do JDK pode ser visualizada como um grafo. A representação completa do grafo de módulos do JDK é representada com uma redução transitiva para facilitar o entendimento, omitindo arestas redundantes.

A visualização do grafo do módulo de banco de dados que estamos explorando seria o seguinte:

grafo-modulo-database.png

A imagem gerada pelo NetBeans nos reforça que, todos os módulos dependem implicitamente do java.base. Como nosso módulo declara um requires em java.sql, há uma aresta no sentido do nosso módulo para o java.sql. Ainda é possível analisar a presença de dois módulos não declarados, java.logging e java.xml. Isso acontece devido a resolução de módulos localizar dependencias adicionais necessárias para satisfazer todas as dependências necessárias ao projeto. Porém, há um detalhe no que diz respeito a visibilidade implicita ou visibilidade transitiva.

Visibilidade transitiva

Para explicar a visibilidade transitiva, vamos imaginar a utilização de nossos módulo recém criado. Teríamos uma aplicação com módulo br.com.esig.app, que para utilizar o nosso módulo iria indicar via module-info.java:

//module-info.java
module br.com.esig.app {
    requires br.com.esig.database;
}

// br.com.esig.app.MapeadorLinhaPessoa.java
import br.com.esig.database.MapeadorLinha;
import java.sql.ResultSet; // Erro aqui
public class PessoaMapeadorLinha implements  MapeadorLinha<Pessoa>{
    public Pessoa mapear(ResultSet res, int index) {...}
}
// PessoaMapeadorLinha.java:3: error: package java.sql is not visible
// import java.sql.ResultSet; 
//  (package java.sql is declared in module java.sql, 
//   but module br.com.esig.app does not read it)
// 1 error

Perceba que, ao compilar a classe MapeadorLinhaPessoa um erro de compilação ocorre. Embora a aplicação possa visualizar os tipos do módulo br.com.esig.database, ela não consegue visualizar os tipos do módulo java.sql. Essa visibilidade implicita é em muitas situações desejada. Isso, porque o módulo br.com.esig.database depende do java.sql, não apenas porque sua implementação contém tipos definidos nestes módulos, mas também por definir estes tipos em assinaturas de APIs públicas (Caso do parâmetro ResultSet do MapeadorLinha).

Para corrigir esta situação, precisamos definir uma visibilidade transitiva ao módulo java.sql para quem utilizar br.com.esig.database. Dessa forma, nosso módulo de banco de dados ficaria da seguinte forma:

module br.com.esig.database {
    requires transitive java.sql;
    exports br.com.esig.database;
}

Situação semelhante ocorre em outros módulos da própria plataforma, como vimos anteriormente, o java.sql necessita dos módulos java.logging e java.xml. Na API pública da interface java.sql.Driver, temos um método que retorna uma instância de Logger que é um tipo declarado e exportado no módulo java.logging. Dessa forma, a declaração correta do módulo deve utilizar requires transitive.

module java.sql {
    requires transitive java.logging;
    requires transitive java.xml;

    exports java.sql;
    exports javax.sql;
    exports javax.transaction.xa;
    ...
}

Compatibilidade com versões anteriores

Como citado anteriormente, o projeto só foi aplicado às regras de encapsulamento do JigSaw no momento da inclusão do module-info.java. Isso permite que aplicações existentes sejam migradas para uma arquitetura modular de forma flexivel e gradual. Quando uma aplicação executando no Java 9 necessita de um tipo que não está definido em nenhum módulo (Ex:Um JAR gerado em versões anteriores), o sistema de módulos tenta carregar o tipo pelo class path. Se for possível, então o tipo carregado é incluído em um módulo especial chamado unnamed module, para que dessa forma seja garantido que todos os tipos estejam associados com um módulo.

Um código de qualquer tipo presente no class path irá ser capaz de acessar todos os tipos exportados de todos os demais módulos construídos pela plataforma Java. Dessa forma, uma aplicação que foi compilada e que executa no Java 8 irá, portanto, compilar e executar da mesma forma no Java 9, desde que ele utilize apenas código da plataforma e classes não depreciadas.

Além disso, unnamed module exporta todos os seus pacotes. Isso significa que um código presente em um módulo pode acessar um tipo definido em um unnamed module desde que presente no class path. Por fim, um módulo não é capaz de declarar uma dependência sobre o unnamed module.

Conclusão

Nesta segunda parte dos fundamentos do Java Modular, foi apresentado como o Java 9 irá aplicar a modularização em nível de código, apresentando as instruções requires, exports e requires transitive. Além disso, foi apresentado a visão em grafo dos módulos e a problemática da visibilidade transitiva. Por fim, abordado alguns pontos sobre compatibilidade para execução de código com versões anteriores ao Java 9 e o conceito de unnamed module.

Se você gostou, deixe um comentário e compartilhe com seus amigos. Até a próxima.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: