Jshell e Read-Eval-Print Loop (REPL)

Atualmente, estou me preparando para o exame OCP 1Z0-813  e venho aqui falar  um pouco mais sobre um recurso que está sendo muito útil para meus estudos: o Jshell do JDK9!


O Jshell é uma proposta de implementar uma ferramenta de programação interativa, conhecida como Read-Eval-Print Loop (REPL), que continuamente lê a entrada do usuário, avalia, e escreve resultado ou a descrição da mudança de estado causada pela entrada. Linguagens como Python, Ruby, Javascript possuem REPLs que permitem a escrita de pequenos programas e até mesmo uma introdução rápida na linguagem.

Estas características ajudaram na popularidade destas linguagens no ensino de linguagem de programação e até algoritmos. Sendo assim, na próxima versão do Java teremos o REPL pelo Jshell para, quem sabe, motivar a utilização do Java no ensino.

Exemplo do Jshell

Um dos itens de avaliação da OCP é a manipulação dos atributos de arquivos pela API do NIO2. No uso da nova API somos testados sobre coisas do tipo:

  • Qual o tipo do arquivo? Um link? Arquivo regular? um diretório?
  • Qual a visibilidade e acessibilidade do arquivo? Ele é oculto? É executável?
  • Qual o usuário que possui privilégios no arquivo?

Estas e outras perguntas são expostas em métodos da API Files incluída ainda no Java 7 por meio de vários métodos helpers. Caso deseje entender um pouco das razões para criação desta API e sua estruturação, aqui e aqui tem um link interessante.

Voltando ao JShell, o primeiro passo é iniciá-lo é pela execução do comando jshell:

Ao tentar executar o método Paths.get, na linha 6, nos deparamos com um erro de “cannot find symbol“! Um erro que indica que a classe não é reconhecida em tempo de compilação. Isso ocorre devido o pacote java.nio.file não ser importado por padrão no JShell.

Para importar um pacote podemos fazer da mesma forma de código Java pela instrução import. O bacana do jshell que ao apertar TAB um autocomplete é apresentado! Os pacotes importados podem ser visualizados pelo comando /imports

Após importar o pacote, podemos utilizar as classes presentes no java.nio.file apenas chamando a instrução desejada;

Na linha 02-03 é apresentado a saída (Print) do resultado da avaliação (Eval) da nossa entrada (Read). Como o método Paths.get retorna uma instância de Path, o shell armazena o valor do resultado automaticamente em uma variável $N onde N é incrementado a cada execução. Podemos utilizar o objeto $2 para chamar qualquer método de Path ou passar ele como parâmetro para outros métodos, por exemplo:

Na linha 01, após digitar TAB, os métodos disponíveis na variável $2 são apresentados em autocomplete. Na linha 07, invocamos o método getFileName da interface Path. Por fim, na linha 11, testamos se o caminho do arquivo é um diretório pela chamada do método Files.isDirectory. Neste último caso, armazenamos o retorno da chamada em uma variável mais amigável.

Conclusão

O Jshell é uma ótima ferramenta para testes rápidos de API e com certeza irá trazer muitos benefícios aos desenvolvedores Java! Este artigo apresentou o básico da nova ferramenta e, caso você tenha gostado e deseja conhecer outras funcionalidades na prática, realize o download do acesso antecipado ao JDK 9 e bons estudos!

Aplicação prática do Padrão Proxy

A ideia do padrão Proxy é simples,  dado um objeto do nosso sistema com um método foo, ao realizar uma chamada pojo.foo(), o método foo do POJO é acessado diretamente

Para aplicar o padrão precisamos inserir um objeto intermediário, que será do mesmo tipo do objeto pojo – implementando as mesmas interfaces ou herdando da mesma classe do pojo – que irá receber todas as chamadas aos métodos e realizar as devidas verificações e processamentos.

Para criar este objeto intermediário, devemos implementar uma classe com a devida equivalência de tipos ou, podemos criar dinamicamente uma classe que implemente as devidas interfaces/super classe.

Aplicação Prática do Proxy

Na linguagem Java,  é possível criar os chamados proxies dinâmicos por meio da API de Proxy. Porém, esta forma apresenta algumas restrições, pois permite apenas a criação de Proxy de interfaces. Ou seja, nossos objetos devem obrigatoriamente implementar uma interface comum  (Que é criada dinamicamente pelas interfaces informadas em tempo de execução).

Um cenário onde o padrão é aplicado é em um Pool de Conexões.  Ao obter uma conexão de uma fonte de dados (java.sql.DataSurce), a aplicação realiza suas leituras/escritas no banco de dados e, quando não é mais necessária,  fecha a conexão pela chamada ao método close da interface java.sql.Connection. A imagem abaixo apresenta o ciclo de vida de uma conexão em um pool.

Sendo assim, para controlar esta devolução da conexão ao pool, e não fechar a conexão de fato, é possível utilizar o padrão proxy. O exemplo de código apresentado a seguir é baseado no projeto flexy-pool.

A linha 0 1-6 temos a criação do Proxy dinâmico por meio do método estático java.lang.reflect.Proxy#newProxyInstance. O primeiro argumento é o classloader onde o proxy criado será carregado, normalmente é passado o mesmo classloader do objeto this. Já o segundo, é a lista de interfaces que serão implementadas dinamicamente pela instância do proxy. Estas interfaces  definem a tipagem do objeto criado, ou seja,  quais métodos serão possíveis invocar e, quais tipos ele pode ser atribuído. Em nosso exemplo, a interface javax.sql.Connection.

O último parâmetro é uma instância da interface InvocationHandler, implementada pela classe ConnectionInvocationHandler em nosso exemplo. Cada instância de Proxy deve possuir um Invocation Handler associado. Quando qualquer método é chamado em uma instância do Proxy,  a chamada é encaminhada para o método invoke do Invocation Handler, que recebe informações de qual método do foi chamado e quais foram os parâmetros fornecidos. Com o uso desta classe, podemos intermediar a comunicação com o objeto encapsulado.

A implementação do método apresentado faz com que, quando um método close for chamado pela aplicação, a conexão seja devolvida ao pool. A lógica que implementa este algoritmo é delegada para uma instância de ConnectionCallback, uma interface própria do flexy-pool. Quando qualquer outro método da interface Connection for invocado, o invocation handler irá delegar a chamada para o objeto encapsulado, que ocorre  na linha 23, via API de reflexão.

Considerações finais

É possível aplicar proxies em muitos outros cenários, para citar algumas aplicações reais, temos o módulo spring-aop, do Spring framework, que utiliza como base para criação dos Aspectos. Um exemplo bacana do uso de proxies no Spring framework é utilizado pelo projeto Spring Data para transformar nossas interfaces de Repositório – “magicamente” – em algo funcional. Por falar em “data”, o Hibernate utiliza para implementar a recuperação de informações Lazy-loading. Outro exemplo é encontrado nos frameworks de testes, tais como Mockito, utilizam proxies para criação de objetos Mocks.

Por fim, caso seja desejado criar proxies de classes, podemos fazê-lo utilizando bibliotecas de terceiros, como a cglib, javassist e byte buddy que são responsáveis por outras atividades além da criação de proxies, em especial, manipulação de bytecode.

 

Padrão Factory

A criação de objetos pode ser algo extremamente complicado tanto que há vários padrões de projetos que tentam resolver de forma elegante e padronizada alguns destes problemas. Estes padrões de projetos são chamados de padrões de criação. A linguagem Java utiliza alguns destes padrões em várias APIs, hoje vamos discutir um pouco do padrão Factory.

No Java 7 fomos introduzidos à um conjunto de APIs denominada NIO.2 que define interfaces e classes para manipulação de arquivos. Em especial, a versão introduziu a interface java.nio.file.Path que visa substituir a API java.io.File para manipulação de arquivos.

Quando trabalhamos com sistemas de arquivos, temos de manipular arquivos e diretórios, seja por caminhos absolutos ou relativos, mas também temos diversas particularidades entre um sistema de arquivos e outro. Por exemplo, no Linux o caminho é absoluto se ele começar com / mas no Windows isso não é verdade. No Windows, o caminho é absoluto se ele começar com uma referência à uma partição, algo como C:.

É por esta razão que a API criada nos disponibiliza uma interface ao invés de uma classe. A implementação irá variar de sistema de arquivos para sistema de arquivos. Sendo assim, Como podemos escrever um código que cria objeto cujo o tipo específico só poderá ser conhecido em tempo de execução?

Seria uma complexidade desnecessária deixar a responsabilidade para o desenvolvedor e para simplificar a criação de uma instância de java.io.file.Path foi criada uma fábrica de Path chamada java.io.file.Paths.

Path path1 = Paths.get("C:\\lucas");		
Path path2 = Paths.get("/home/lucas");

System.out.println(path1.isAbsolute());
System.out.println(path2.isAbsolute());

//Output no Linux:
//false
//true

Perceba que o resultado deste método vai depender do sistema de arquivos sob o qual a JVM está executando e toda a complexidade possivelmente envolvida na criação de cada objeto Path é encapsulada pela fábrica.Algumas características de fábricas no Java são a utilização de métodos estáticos. Alguns exemplos de fábricas são java.nio.file.FileSystems e java.util.concurrent.Executors.

Perceba que existe uma nomenclatura comum nestas classes que terminam com um s no final. Mas isso não é um padrão. Ainda no NIO.2 temos uma classe java.nio.file.Files que é considerada uma classe utilitária. Classes utilitárias também são compostas por métodos estáticos mas diferem das fábricas por não criar objetos mas por manipular ou criar novos objetos de instâncias existentes.

Path path = Paths.get("/home/lucas");
Files.exists(path);

//Output no Linux:
//true

No exemplo acima, utilizamos a classe Files para chamar o método estático exists que opera em instâncias de java.nio.file.Path para verificar se o arquivo/diretório existe no sistema de arquivos.

Considerações finais

Reconhecer os padrões de projetos utilizados na linguagem é uma ótima forma de melhorar suas atividades de codificação. O padrão Factory é utilizado desde APIS SE até EE. Neste technote discutimos um pouco sobre a aplicação do padrão Factory na API NIO.2 e apresentamos algumas as operações que você costumava fazer com java.io.File e que é possível realizar de forma mais simplificada com esta API.

Até mais!

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.

Modularidade com Java 9 – Parte 1

Neste primeiro momento vamos falar sobre a motivação principal funcionalidade que será liberada no Java 9 que é o sistema de módulos chamado Project Jigsaw. O nome já diz tudo, Jigsaw em inglês significa Quebra-cabeça e essa é a ideia do Projeto!

Este texto é a primeira parte de um conjunto de publicações das anotações dos meus estudos sobre as novidades do Java 9. Vamos lá!

Motivação

A linguagem Java até o momento possui alguns modificadores de acesso que são utilizados para definir a visibilidade dos componentes que são public, protected, private e package. Quando desejamos desenvolver uma biblioteca que será disponibilizada via .jar precisamos compartilhar alguns vários componentes para todos e muito provavelmente precisamos reutilizar alguns destes componentes internamente entre os pacotes do nosso próprio código. Para isso nós precisamos definir nossa classe e métodos com o modificador de acesso public, já que nenhum dos outros modificadores permite este comportamento..

O problema é que desta forma estamos compartilhando com todos alguns artefatos que deveriam fazer sentido apenas dentro do módulo mas que precisaram ser públicos para permitir a comunicação entre pacotes. Isso afetou inclusive a própria plataforma que teve de expor as classes de implementação interna do JDK e que são utilizadas para construção de ferramentas como a objenesis que tem a finalidade de criar objetos sem invocar o construtor por meio de uma classe chamada sun.misc.Unsafe.

Embora a utilização de classes do pacotes sun.* não seja recomendada e Unsafe tenha sido projetada como um singleton de construtor privado e acessível via um método de acesso que verifica até o ClassLoader que está chamando o método, o pessoal consegue burlar este mecanismo de segurança aplicado ao singleton via reflection!

O código acima, em geral, define a visibilidade à um atributo privado da classe Unsafe chamado theUnsafe que é a referência singleton da classe! Esta classe permite realizar outras coisas bastante “estranhas” como por exemplo, lançar exceções verificadas em um método sem a necessidade de declarar a exceção, até coisas mais perigosas como atuar diretamente na alocação de memória nativa!

Sendo assim, o JDK 9 irá incluir um novo elemento na linguagem, que são os módulos, que vai expandir o entendimento da visibilidade ‘estática’ public em três situações:

  • público para todos
  • públic para módulos específicos
  • público apenas dentro do módulo.

Dessa forma o operador public não significa mais que sua classe estará acessível para toda JVM! Esta encapsulação forte é a possibilidade de um componente declarar quais dos seus tipos públicos serão acessíveis para outros componentes e quais não. Ou seja, o desenvolvedor não será capaz nem ao menos de importar uma classe de um pacote que não seja exportado, causando erro de compilação.

Esta funcionalidade irá beneficiar, além da própria implementação da plataforma Java que irá poder ocultar componentes e utilizá-los internamente sem preocupação, os desenvolvedores de aplicações, bibliotecas.

Ainda sobre a acessibilidade, a API de reflexão também irá respeitar as regras da imposta pelo sistema de módulos. As operações de reflexão irão realizar as mesmas checagens realizadas pelo compilador em tempo de execução. Ou seja, ao chamar métodos da API como o setAcessible(true) em um objeto qualquer, será verificado se a classe do objeto está em um módulo diferente e se o pacote que define a classe foi exportado para o módulo em questão. Com isso, situações de acesso indevido de objetos como a sun.misc.Unsafe não devem mais ocorrer.

Considerações finais

Vimos que alguns problemas enfrentados no encapsulamento de componentes no Java e como o Java pretende tratar esta situação com o uso do sistema de módulos introduzido no JDK 9. Nos próximos posts será descrito outros assuntos relacionados, entre eles:

  • Resolução de módulos e Grafo de módulos
  • Legibilidade e Acessibilidade Implícita de Componentes
  • Detalhes da sintaxe de um módulo.

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