[Pesquisar este blog]

segunda-feira, 8 de janeiro de 2018

Java 9::Jigsaw - a nova modularidade da plataforma (Parte III)

O Jigsaw trouxe um novo e interessante sistema de modularização para a versão 9 do Java, o qual soluciona muitos problemas das versões anteriores, além de proporcionar novas facilidades na construção de aplicações.
A parte III deste post traz um exemplo simples que mostra o essencial da construção de aplicações modularizadas, incluindo o uso do jlink. para a criação de imagens executáveis (runtime images), isto é, máquinas virtuais Java customizadas. A parte I abordou o conceito de modularidade e a especificação do novo artefato module trazida pelo Jigsaw; enquanto a parte II discute a implementação do Jigsaw na plataforma Java.
Ao longo deste post, use ';' como separador de diretório no Microsoft Windows e ':' no Unix/Linux. Note que %JAVA_HOME%/jmods é o diretório, no Windows que contém o módulo java.base.jmod (e os demais módulos padrão do JDK). Verifique sua instalação em caso de dúvidas.

Aplicação Simples (módulo único)

Uma aplicação simples é constituída por classes distribuídas em um pacote (ou mais) encapsulados em um único módulo. Além disso, estas classes não serão utilizadas por outras aplicações, ou seja, não serão exportadas para outros módulo, ou, em outros termos, observáveis por outros módulos.

Criaremos uma aplicação no estilo clássico do "Hello World" para começar.

1.
Crie um diretório Jigsaw em um lugar conveniente de seu sistema de arquivos. Entre neste diretório.
md Jigsaw
cd Jigsaw

2.
Crie um subdiretório para o código fonte (src), com subdiretórios para cada pacote e subpacote necessários, usando o seguinte esquema: src\<nome.pacote>\<subdir1>[\<subdir2>...].
Neste exemplo será necessário apenas um pacote (cujo nome longo é apenas ilustrativo).
md src\br.gov.sp.fatec.saudacoes\br\gov\sp\fatec\saudacoes

3.
Neste subdiretório crie o arquivo Main.java com o conteúdo que segue. Este é o código da aplicação simples que estamos construindo.
package br.gov.sp.fatec.saudacoes;

public class Main {
   public static void main(String[] args) {
      System.out.println("Olá Jigsaw!");
   }
}

4.
Navegue para o subdiretório que denomina o pacote (src\br.gov.sp.fatec.saudacoes):
cd ..\..\..\..\..

5.
Neste subdiretório crie o arquivo module-info.java, que é o descritor do módulo br.gov.sp.fatec.saudacoes, com o conteúdo que segue.
module br.gov.sp.fatec.saudacoes {
}

6.
Navegue para o diretório inicial (Jigsaw):
cd ..\..

7.
Crie um subdiretório para os módulos compilados (modules).
md modules\br.gov.sp.fatec.saudacoes


8.
Compile os arquivos de cada subpacote (apenas um neste exemplo).
(O comando abaixo foi separado em várias linhas para melhorar a legibilidade, mas deve ser fornecido em uma única linha!)
javac -d modules\br.gov.sp.fatec.saudacoes
      src\br.gov.sp.fatec.saudacoes\br\gov\sp\fatec\saudacoes\Main.java

A opção -d é seguida do diretório onde o código binário das classes deve ser armazenado, respeitando a estrutura de pacotes. O último argumento é o nome do arquivo que será compilado.

9.
Compile o arquivo de informação de cada módulo (apenas um neste exemplo).
(O comando abaixo foi separado em várias linhas para melhorar a legibilidade, mas deve ser fornecido em uma única linha!)
javac -d modules\br.gov.sp.fatec.saudacoes
      src\br.gov.sp.fatec.saudacoes\module-info.java

10.
É possível usar a JVM para listar os módulos disponíveis.
java --module-path modules --list-modules
A opção --module-path é seguida de uma lista de diretórios que contém os módulos não integrantes do JDK/JRE. A opção --list-modules determina a ação a ser tomada pela JVM.

11.
E também usar a JVM para obter informação de módulos observáveis.
(O comando abaixo deve ser fornecido em uma única linha!)
java --module-path modules
     --describe-module br.gov.sp.fatec.saudacoes

Aqui a opção --module-path é seguida de uma lista de diretórios que contém os módulos não integrantes do JDK/JRE. Já a opção --describe-module deve ser seguida do nome do módulo que deve ser descrito.
O resultado deve ser como segue:
br.gov.sp.fatec.saudacoes file:///C:/Users/Jandl/workspace/Jigsaw/modules/br.gov.sp.fatec.saudacoes/
requires java.base mandated
contains br.gov.sp.fatec.saudacoes

Observe que o módulo br.gov.sp.fatec.saudacoes requer (obrigatoriamente) o módulo java.base, apesar de seu descritor não explicitar tal necessidade.

12.
Para executar a aplicação simples, organizada como um módulo, usamos o comando que segue.
(O comando abaixo deve ser fornecido em uma única linha!)
java --module-path modules
     -m br.gov.sp.fatec.saudacoes/br.gov.sp.fatec.saudacoes.Main

A opção --module-path é seguida de uma lista de diretórios que contém os módulos não integrantes do JDK/JRE; enquando a opção -m deve ser seguida pelo nome do módulo e da classe que deve ser executada, mas sempre usando o separador '/'.
O resultado deve ser como segue:
Olá Jigsaw!
A listagem dos módulos disponíveis, conforme o passo 10, deve ser semelhante à figura que segue.
O resultado da aplicação simples construída, executada como indicado no passo 12, deve ser como na próxima figura.

Este exemplo mostrou como construir uma aplicação simples, constituída de um único módulo, com o novo sistema de modularização da plataforma Java.

Aplicação e Módulo-Recurso

Uma situação bastante mais típica é uma aplicação constituída de vários módulos. Nestes casos, sempre existe um módulo principal e outros módulos que se comportam como recursos do módulo principal.

Nesta parte do exemplo, criaremos uma aplicação, ainda no estilo clássico do "Hello World", mas que utiliza uma classe auxiliar de um pacote residente em um outro módulo, ou seja, a aplicação utiliza um módulo adicional como recurso.

1.
Navegue para o diretório inicial (Jigsaw).

2.
Crie novos subdiretórios para o novo pacote jandl.tecnopode.
md src\jandl.tecnopode\jandl\tecnopode

3.
Navegue para o subdiretório src\jandl.tecnopode\jandl\tecnopode.
cd src\jandl.tecnopode\jandl\tecnopode

4.
Neste subdiretório crie o arquivo Saudacoes.java com o conteúdo que segue.
package jandl.tecnopode;

public class Saudacoes {
   private String message;

   public Saudacoes() {
      this("Ola Jigsaw!");
   }

   public Saudacoes(String msg) {
      setMessage(msg);
   }

   public String getMessage() {
      return message;
   }

   public void setMessage(String msg) {
      message = msg;
   }

   @Override
   public String toString() {
      return getMessage();
   }
}

4.
Navegue para o subdiretório que denomina o pacote (src\jandl.tecnopode):
cd ..\..

5.
Neste subdiretório crie o arquivo module-info.java, que é o descritor do módulo jandl.tecnopode, com o conteúdo que segue.
module jandl.tecnopode {
   exports jandl.tecnopode;
}

6.
Navegue para o diretório inicial (Jigsaw).
cd ..\..

7.
Compile os arquivos do novo pacote e do descritor do módulo.
(Forneça os comandos que seguem numa única linha!)
javac -d modules\jandl.tecnopode
      src\jandl.tecnopode\jandl\tecnopode\Saudacoes.java
javac -d modules\jandl.tecnopode
      src\jandl.tecnopode\module-info.java

É possível compilar tudo junto:
javac -d modules\jandl.tecnopode
      src\jandl.tecnopode\jandl\tecnopode\Saudacoes.java
      src\jandl.tecnopode\module-info.java

8.
Verifique se o novo módulo é observável pela JVM com:
java --module-path modules --describe-module jandl.tecnopode

9.
Navegue até o diretório do subpacote src\br.gov.sp.fatec.saudacoes.
cd src\br.gov.sp.fatec.saudacoes\br\gov\sp\fatec\saudacoes

10.
Neste subdiretório crie o arquivo Main2.java, que corresponde a nova aplicação, com o conteúdo que segue.
package br.gov.sp.fatec.saudacoes;
import jandl.tecnopode.Saudacoes;

public class Main2 {
   public static void main(String[] args) {
      Saudacoes sds = new Saudacoes("Ola novamente Jigsaw!");
      System.out.println(sds);
   }
}

Observe que a classe Saudacoes, do novo pacote jandl.tecnopode, é importada como usualmente feito.

11.
Navegue para o diretório inicial (Jigsaw).
cd ..\..\..\..\..\..\..

12.
Compile a nova aplicação (Main2.java).
(O comando abaixo deve ser fornecido em uma única linha!)
javac --module-path modules
      -d modules/br.gov.sp.fatec.saudacoes
      src/br.gov.sp.fatec.saudacoes/br/gov/sp/fatec/saudacoes/Main2.java
src\br.gov.sp.fatec.saudacoes\br\gov\sp\fatec\saudacoes\Main2.java:2: error: pac
kage jandl.tecnopode is not visible
import jandl.tecnopode.Saudacoes;
            ^
  (package jandl.tecnopode is declared in module jandl.tecnopode, but module br.
gov.sp.fatec.saudacoes does not read it)
1 error

O erro obtido se deve ao fato do módulo da aplicação não ler/importar o módulo recurso.

13.
Navegue para o subdiretório inicial do pacote br.gov.sp.fatec.saudacoes.
cd src\br.gov.sp.fatec.saudacoes

14.
Edite o descritor de módulo module-info.java como segue, para indicar a importação do módulo que contém os recursos necessários.
module br.gov.sp.fatec.saudacoes {
   requires jandl.tecnopode;
}

15.
Navegue para o diretório inicial (Jigsaw).
cd ..\..\

16.
Compile novamente a nova aplicação (Main2.java) e também o descritor de seu módulo.
(O comando abaixo deve ser fornecido em uma única linha!)
javac --module-path modules
      -d modules/br.gov.sp.fatec.saudacoes
      src/br.gov.sp.fatec.saudacoes/br/gov/sp/fatec/saudacoes/Main2.java
      src/br.gov.sp.fatec.saudacoes/module-info.java

17.
Execute a nova aplicação.
(O comando abaixo deve ser fornecido em uma única linha!)
java --module-path modules
      -m br.gov.sp.fatec.saudacoes/br.gov.sp.fatec.saudacoes.Main2

O resultado da nova aplicação é como o que segue.
Ola novamente Jigsaw!

Este segundo exemplo mostrou como construir uma aplicação ainda simples, embora mais realista, pois é constituída de um módulo que utiliza recursos de outro módulo. 

Na sequência serão exploradas outras possibilidade que exibem algumas das vantagens do novo sistema de modularização.

Empacotamento dos módulos

Até agora, a compilação dos módulos construídos gerou uma árvore de subdiretórios contendo o código binário das classes distribuídos em seus respectivos subdiretórios. Em nosso projeto o subdiretório Jigsaw\src contém a árvore do código fonte dos tipos pertencentes a cada um dos módulos criados, enquanto Jigsaw\modules contém a árvore do código binário dos respectivos tipos. Claramente esta estrutura é pouco adequada para distribuição do código das aplicações.

A ferramenta de linha de comando jar, que existe desde a primeira versão do Java, foi atualizada para possibilitar a criação de módulos, ou seja, permite empacotar os tipos e descritor de um módulo na forma de um arquivo JAR.

Convém criar um novo subdiretório no projeto para conter os módulos empacotados a partir do diretório inicial (Jigsaw).
md library

Para criar um módulo, use a ferramenta jar como segue:
(O comando abaixo deve ser fornecido em uma única linha!)
jar --create
    --file=library/jandl.tecnopode@1.0.jar
    --module-version=1.0
    -C modules/jandl.tecnopode .

A opção --create é autoexplicativa; --file determina o caminho e o nome desejado para o módulo que será criado; --module-version especifica a versão do módulo; -C indica o diretório onde existe a árvore do código binário do módulo.

jar --create
    --file=library/br.gov.sp.fatec.saudacoes@1.0.jar
    --main-class=br.gov.sp.fatec.saudacoes.Main2
    -C modules/br.gov.sp.fatec.saudacoes .

Aqui deve ser observado o uso da opção --main-class seguida do nome da classe principal do módulo (aquela que é executada por padrão).

A figura que segue ilustra o resultado do empacotamento dos módulo br.gov.sp.fatec.saudacoes e jandl.tecnopode.


Com o empacotamento, a distribuição de uma aplicação se resume ao envio dos módulos necessários, que podem ser usados em qualquer outra instalação do Java 9. Para executar a aplicação, basta fornecer:
java -p library -m br.gov.sp.fatec.saudacoes

A opção -p indica uma lista de diretórios que contém os módulos necessários; enquanto -m especifica o módulo que (classe principal) terá o início executado, como mostra a figura que segue.


A ferramenta jar também pode ser usada para descrever um módulo empacotado, como exemplificado nos comandos que seguem.

jar --describe-module --file=library/jandl.tecnopode@1.0.jar
jar --describe-module --file=library/br.gov.sp.fatec.saudacoes@1.0.jar


Criação de Imagem Executável da Aplicação

Uma novidade muito interessante do Java 9 é a possibilidade de criação de imagens executáveis (runtime images) de aplicações. Uma imagem executável contém uma JVM, os módulos específicos de uma aplicação e todas as suas dependências, inclusive as transitivas (decorrentes da execução), permitindo que a aplicação seja executada em qualquer ambiente compatível, independente destes possuírem ou não instalações compatíveis do Java.

A JVM provida numa imagem executável contém apenas os módulos necessários para a aplicação, ou seja, aqueles não utilizados não são inclusos, reduzindo seu tamanho, constituindo um ambiente customizado e independente, pois não requer a presença de um JRE ou JDK para sua execução.

Apenas a arquitetura do sistema deve ser compatível, ou seja, as imagens executáveis construídas para uma arquitetura específica de SO só podem ser utilizadas em tais SOs, tal como ocorre com programas executáveis gerados para o Microsoft Windows, que não são compatíveis com SO Linux e outros.

Ainda assim, a distribuição de uma imagem executável pode simplificar muito seu uso, pois seus usuário não precisam se preocupar com os pré-requisitos de instalação necessários.

O jlink é a nova ferramenta de linha de comando capaz de criar imagens executáveis, como no exemplo de uso que segue.
(O comando abaixo deve ser fornecido em uma única linha!)
jlink --module-path %JAVA_HOME%\jmods;library
      --add-modules br.gov.sp.fatec.saudacoes
      --output jigsawDemoApp

A opção --module-path é seguida pela lista de diretórios de módulos necessários (no caso o subdiretório jmods na instalação local do Java 9 e o subdiretório library do nosso projeto). A opção --add-modules indica o módulo da aplicação que será adicionado na imagem. A opção --output indica o subdiretório onde a imagem obtida será gerada. Note que as dependências do módulo br.gov.sp.fatec.saudacoes (ou seja, os módulos java.base e jandl.tecnopode) são automaticamente identificadas e adicionadas à imagem.

A imagem gerada terá a seguinte estrutura:
jigsawDemoApp
├───bin
│   └───server
├───conf
│   └───security
│       └───policy
│           ├───limited
│           └───unlimited
├───include
│   └───win32
├───legal
│   └───java.base
└───lib
    ├───security
    └───server

Em sua raiz existe um arquivo release que contém a versão do Java e os módulos inclusos na imagem executável. No diretório lib encontramos o arquivo modules com apenas 21.8MB, bem menor que o correspondente da JVM completa da versão 9.

Para executar a aplicação encapsulada na imagem executável, basta fornecer o comando que segue:
jigsawDemoApp\bin\java -m br.gov.sp.fatec.saudacoes/br.gov.sp.fatec.saudacoes.Main2

Nele utiliza-se a JVM da própria imagem, seguida da opção -m e da combinação do nome do módulo (br.gov.sp.fatec.saudacoes), do separador '/' e do nome da classe principal  da aplicação (br.gov.sp.fatec.saudacoes.Main2).

Também é possível solicitar que a ferramenta jlink gere um pequeno script de inicialização, mais fácil de ser usado. Para criar a imagem executável e também gerar tal script, o comando adequado é:
jlink --module-path %JAVA_HOME%\jmods;library
      --add-modules br.gov.sp.fatec.saudacoes
      --output jigsawDemoApp
      --launcher launch=br.gov.sp.fatec.saudacoes/br.gov.sp.fatec.saudacoes.Main2

Observe a adição da opção --launcher launch=<modulo>/>classePrincipal>.

A ativação da aplicação agora pode ser feita de maneira bem mais simples com:
jigsawDemoApp\bin\launch

Conclusões

Os exemplos deste post, embora muito simples, mostram como utilizar de maneira proveitosa o novo sistema de modularização do Java. Em sua primeira parte mostra como criar módulos (modules). Na segunda parte mostra como construir aplicações constituídas de vários módulos.

Além disso, estes exemplo permitem perceber como a distribuição de aplicações é simplificada tanto pelo uso de módulos, como pela criação de imagens executáveis (runtime images). Módulos podem ser distribuídos diretamente, permitindo o reuso em diferentes aplicações, funcionando como componentes. No entanto o uso dos módulos requerem um ambiente de execução Java  previamente instalado. Já a distribuição de imagens executáveis é muito conveniente, pois não requerem ambientes de execução pré-instalados, pois já contém uma JVM própria. Além disso, as imagens executáveis permitem a customização das JVM para aplicações específicas, o que é algo bem interessante.

Isto tudo permite afirmar que o aguardado projeto Jigsaw e seu novo sistema de modularização é, de fato, uma valiosa adição na plataforma Java!


Para Saber Mais


quarta-feira, 3 de janeiro de 2018

Java 9::Jigsaw, a nova modularidade da plataforma (Parte II)

Depois de mais de oito anos de trabalho, o projeto Jigsaw e o novo sistema de modularidade para a plataforma Java estreiam na versão 9, trazendo um grande conjunto de novas possibilidades para o desenvolvimento de software.
Desenvolvimento modular com Java 9
Desenvolvimento modular com Java 9
A parte I deste post abordou o conceito de modularidade e a especificação do novo artefato module trazida pelo Jigsaw.

Agora, na parte II, será discutida a implementação do Jigsaw na versão 9 do Java. Para finalizar este post, na parte III, temos um exemplo de construção de uma aplicação modular.

O JDK modularizado

Uma das queixas frequentes sobre a plataforma Java era o fato de sua API nativa ser constituída de um monólito de código gigantesco. Estamos falando do famigerado arquivo rt.jar (Java Runtime archive). Qualquer aplicação Java executada na versão 8 (ou anterior), requer que o rt.jar esteja disponível, pois nele residem todas as classes e interfaces padronizadas da máquina virtual. Como tal arquivo é grande, o trabalho da JVM para localizar e extrair qualquer tipo necessário para a aplicação em execução é, obviamente, mais demorado do que a execução destas mesmas tarefas em arquivos menores.

A versão do rt.jar localizada no subdiretório lib do JRE possuia 51,9MB no JRE 1.8.0_121. Já a versão armazenada no subdiretório jre\lib, do JRE incluso no JDK da mesma versão 1.8.1_121, tem 60.4MB. Ambos possuem mais de 19000 arquivos distintos.

Além da mencionada dificuldade no carregamento de tipos, o tamanho do rt.jar é proibitivo em aplicações destinadas a equipamentos que não sejam computadores, por exemplo, para dispositivos "vestíveis" (wearable devices) ou embutidos (embeded devices).

Outro problema, menos discutido, mas igualmente presente, é a inconveniente dependência cíclica, pouco intuitiva, existente entre alguns de seus elementos.

Como já visto, o ponto de partida do projeto Jigsaw foi a criação de um novo artefato module que, conforme a especificação da versão 9, é um arquivo JAR modular. Um arquivo JAR modular é um arquivo JAR que possui um descritor de módulo, module-info.class, no diretório raiz do arquivo JAR. Tal descritor é a forma binária da declaração de um módulo.

Uma das maiores tarefas do projeto Jigsaw foi dividir o arquivo rt.jar em um conjunto adequado de módulos, menores, interdependentes, mas sem dependências circulares. O resultado pode ser visto na figura que segue (que precisa ser ampliada - basta clicar - para que seus detalhes sejam observados).

Hierarquia dos módulos no Java 9

Na parte inferior da figura podemos ver o módulo java.base, o único que não depende de outros - que possui arestas de chegada. Todo e qualquer módulo criado lê (ou depende de) java.base, implícita ou explicitamente, de maneira similar a importação do pacote java.lang.

O módulo java.base exporta pacotes como java.lang, java.util, java.math etc., que são praticamente onipresentes em qualquer aplicação Java.

Os novos módulos são como novos componentes, carregados por meio de um modulepath (que substitui o antigo classpath), mais simples e mais eficiente. Um classpath típico lista os diretórios onde residem os pacotes, sem deixar claro quais são os pacotes necessários dentro de cada um dos diretórios indicados. Já um modulepath lista apenas os módulo necessários, reduzindo a incerteza em relação aos elementos necessários para uma aplicação.

Na versão 9 o JDK é tipicamente instalado num diretório jdk-9, que possui um subdiretório jmods que contém todos os módulos da API Java, resultado do trabalho de modularização do Jigsaw. O modulo java.base está contido no arquivo java.base.jmod que possui apenas 15.8MB. Os arquivos de extensão jmod tem formato compatível ao dos arquivos jar, ou seja, são uma espécie de arquivo compactado no formato ZIP. O tamanho total do subdiretório jmods é 116MB (maior que o antigo rt.jar). Além disso, existe um arquivo modules (sem extensão), no subdiretório lib, com 167MB, que condensa todos os módulos, e cuja existência é garantir a compatibilidade com as aplicações produzidas por versões anteriores ao Java 9.

Já a versão 9 do JRE, instalada num diretório jre-9, existe apenas o subdiretório lib, no qual existe o arquivo modules com 107MB, requerido para executar qualquer aplicação Java. 

A presença do arquivo modules, embora pareça recriar a situação anterior do rt.jar, é uma consequência da retrocompatibilidade garantida pelo Java 9 em relação às versões anteriores. Esta é a configuração default do JDK e do JRE nesta versão. Mas a grande diferença proporcionada pelo Jigsaw está naquilo que pode ser feito além da configuração padrão.

Com a modularização do JDK torna-se possível especificar quais módulos do Java Runtime serão usados, reduzindo o trabalho da JVM na localização de tipos (com menos módulos, menos tipos, menos trabalho no carregamento de classes). Assim, se a aplicação não usa componentes Swing, o módulo java.desktop não precisa ser incluído; se o suporte para Corba não é necessário, nada  de especificar java.corba; e assim por diante. Apenas o módulo java.base é essencial (por isso é carregado implicitamente).

Na prática, isto significa que o Java 9 torna possível customizar a JVM para conter apenas os módulos necessários a um conjunto específico de aplicações; até mesmo otimizando-o para uma única aplicação especial. Quando tal customização não é feita, a configuração default, de compatibilidade mais ampla, é garantida.

jlink

A nova ferramenta de linha de comando jlink permite que sejam escolhidos os módulos e dependências a serem inclusas em uma distribuição, com granularidade muito fina, unindo-os. Isto muito contribui para reduzir e controlar o tamanho das distribuições. 

Abaixo temos o formato típico da linha de comando do jlink, envolvendo o modulepath, os módulos inclusos e a indicação da saída.

> jlink --module-path <modulepath>
       --add-modules <module>[,<module>...]
       --output <path>

Com isto o jlink permite criar um arquivo modules completamente customizado para uma aplicação específica, o que permite otimizar a JVM para ambientes particulares. Dentre as várias opções do jlink é possível indicar o nível de compressão dos módulos, os serviços interligados, plugins, a lista de módulos observados, etc.

Um exemplo de uso do jlink é:

> jlink --module-path %JAVA_HOME%/jmods;projectMod
      --add-modules br.gov.sp.fatec.saudacoes
      --output saudacoesapp

Este comando cria uma imagem runtime que contém o módulo br.gov.sp.fatec.saudacoes, incluindo suas dependências transitivas (isto é, decorrentes de sua execução). O valor de --module-path é o caminho dos diretórios contendo os módulos previamente empacotados. Use ';' como separadores de diretório no Microsoft Windows e ':' no Unix/Linux. Assim, %JAVA_HOME%/jmods é o diretório que contém o módulo java.base.jmod (e os demais módulos padrão do JDK). O diretório projecMod, incluído no modulepath, é aquele que contém os demais artefatos, no caso, o módulo denominado br.gov.sp.fatec.saudacoes. A saída produzida, a imagem da aplicação, será armazenada no diretório saudacoesapp.

Além disso, o jlink permite também controle avançado sobre a ligação entre os módulos, com a possibilidade de escolhe entre ligação estática (static linking), que cria módulos maiores, mas mais rápidos; e ligação dinâmica (dynamic linking), que cria módulos menores, mas com mais tempo de carregamento maior.


Conclusões

O Jigsaw tem um papel central no Java, pois com ele se espera melhorar a modularidade, a eficiência e também a segurança da plataforma. Com ele torna-se possível criar aplicações mais escaláveis, leves, robustas e seguras.

A ambição maior do Jigsaw é incentivar o desenvolvimento de um ecossistema completo baseado na linguagem e na JVM, por meio de um software develpment kit (SDK) modular, para criar aplicações modulares com uso de ferramentas igualmente modulares.

Concretamente o Jigsaw permite solucionar o JAR hell (vide primeira parte do post); obter melhor encapsulamento e segurança entre pacotes e clientes destes pacotes; performance melhorada e tamanho reduzido do ambiente mínimo requerido para aplicações modulares.

Na próxima e última parte deste post será construída uma aplicação modular.

Para Saber Mais



domingo, 17 de dezembro de 2017

Java 9::Jigsaw, a nova modularidade da plataforma (Parte I)

A característica mais esperada da versão 9 do Java é, certamente, o resultado do projeto Jigsaw, que tomou anos de discussões e desenvolvimento, e cuja essência é a melhoria substancial da modularidade da plataforma Java.


Para falar do Jigsaw é necessário, primeiro, discutir a ideia de  modularidade, para, depois, discutir os dois aspectos principais do próprio Jigsaw, que são sua especificação e sua implementação na versão 9 da plataforma Java.

A parte I deste post aborda o conceito de modularidade e a especificação do Jigsaw. A parte II tratará de sua implementação, enquanto a parte III contém um exemplo de seu uso. Estes posts fazem parte de uma série que trata das novidades do Java 9:Primeiras Impressões.

Modularidade

A modularidade é um princípio muito importante no projeto de software que enfatiza a criação de conjuntos de classes ou componentes que possam ser reutilizados em diferentes contextos. Tais conjuntos são considerados os módulos de um sistema de software.

Assim o processo de modularização auxilia: 
  • na ocultação da implementação por meio de encapsulamento forte; 
  • na redução do acoplamento entre componentes; 
  • na simplificação dos contratos entre componentes (de diferentes módulos principalmente); e 
  • na redução e explicitação de dependências entre componentes. 


Modularidade no Java

Até a versão 8, a unidade básica da modularidade no Java eram os arquivos JAR (Java Archives), que nada mais são do que arquivos compactados contendo uma estrutura de subdiretórios e arquivos de classe Java correspondentes a um ou mais pacotes de classes Java, eventualmente incluindo alguns outros tipos de arquivos (até mesmo o próprio código fonte), que podem ser tratados como recursos de aplicações. 

Embora os arquivos JAR sejam capazes de agrupar classes relacionadas, sua organização possui algumas limitações:
  • contratos e dependências explicitas entre os arquivos JAR componentes de uma aplicação;
  • encapsulamento fraco dos elementos contidos nos JARs.

Além disso, o mecanismo de carregamento de classes baseado nos arquivos JAR não impede, restringe ou sequer notifica que múltiplas versões de um mesmo JAR são encontradas no classpath, levando a efeitos indesejáveis e imprevisíveis, um problema sério conhecido como Jar Hell.

Isso sem contar com aplicações que são corretamente compiladas, mas falham em tempo de execução por conta de classes não encontradas (JARs ausentes) ou exibem erros decorrentes de versões incorretas de certas classes (JARs inadequados).

Com estas limitações em mente, os projetistas do Java criaram uma nova construção na linguagem para estabelecer uma nova unidade de modularização que pudesse superar os problemas existentes com os JARs, a qual foi denominada (sem muita criatividade) como module. 

O novo artefato: module

No Java 9, como resultado do projeto Jigsaw, é possível a criação de módulos. Embora os módulos sejam também arquivos JAR, eles são dotados de três propriedades obrigatórias e fundamentais que, explicitamente indicam:
  • o nome do módulo, que é sua identificação (única);
  • suas dependências, isto é, o que este módulo requer; e
  • a definição de sua Application Programming Interface (API), ou seja, o que este módulo fornece/exporta.


O nome do módulo, embora possa ser arbitrário, deve ser único. Por isso é recomendado o uso do esquema conhecido de denominação de pacotes da convenção Java, ou seja, sua URL invertida, tais como org.apache.commons.iocom.google.guava, ou br.gov.sp.cps.fatec.ads.

As dependências de um módulo, ou seja, suas necessidades, são expressas por uma lista de pacotes que precisam ser exportados por um ou mais módulos. Observe que o nome dos módulos exportadores não é necessário, mas apenas os pacotes exigidos. As classes necessárias nestes módulos devem ser públicas.

O módulo também deve indicar os pacotes que são exporta, ou seja, sua API pública. Deve ser notado que classes existentes no módulo, não serão acessíveis externamente se seus pacotes não forem exportados, mesmo que sejam públicas.

Também existem outras duas propriedades, cujo uso esperado não é tão comum, mas que permitem especificar:
  • os serviços consumidos pelo módulo e
  • os serviços providos pelo módulo.

Estas propriedades são usadas apenas quando serviços são providos e consumidos por meio da interface java.util.ServiceLoader.

Estas informações são codificadas num descritor de módulo (module descriptor) que é criado como um arquivo module-info.java, o qual é compilado como module-info.class e empacotado junto os demais elementos do módulo, de modo que a JVM possa recuperar tais informações, permitindo o carregamento de classes e outros recursos, tratando-os como um módulo.

A informação de módulo, contida no arquivo module-info.java, tem a estrutura que segue:

module MODULE_NAME {
   requires MODULE_NAME_1;
   requires MODULE_NAME_2;

   requires MODULE_NAME_N;

   exports PACKAGE_NAME_1;
   exports PACKAGE_NAME_2;

   exports PACKAGE_NAME_N;
}


Como cada módulo contém a informação que necessita dos demais, a questão do Jar Hell se torna coisa do passado, com mostra a figura que segue.


Os módulos (modules) são a construção de mais alto nível no novo sistema de modularização implementado na linguagem, englobando os pacotes (packages). Os desenvolvedores podem organizar seu código em módulos, declarando as dependências existentes entre eles nas definições contidas no arquivo module-info.java.

A nova acessibilidade

Com o novo sistema de modularidade do Java introduzido pelo Jigsaw, a acessibilidade dos tipos existentes sofre uma mudança considerável.

A acessibilidade do Java 8 e das versões anteriores era definida pelos níveis:
  • público, 
  • default
  • protegido e 
  • privado
Tais níveis são indicados por meio dos especificadores de acesso public, protected e private, sendo que a omissão da indicação explícita do nível de acesso é entendida como o nível default, que não tem especificador próprio.

Em resumo, até o Java 8, qualquer tipo público presente no classpath seria acessível por qualquer outro tipo.

No Java 9, com o Jigsaw temos:
  • público para todos que tem acesso ao módulo (exports);
  • público para módulos específicos (comentado na parte 2 do post);
  • público apenas para os demais tipos pertencentes ao módulo, mas não outros externos ao módulo;
  • default;
  • protegido; e
  • privado.
Assim, o novo esquema de modularidade inclui dois níveis extras de acesso público, cujas restrições são associadas ao novo artefato module.

Benefícios esperados

O Jigsaw desempenha um papel central no futuro da plataforma Java, pois provê a base para sua evolução, ao mesmo tempo que garante compatibilidade com o esquema de modularidade anterior, baseado exclusivamente em pacotes.

De fato, um programador pode continuar a utilizar a versão 9 do Java como se o Jigsaw e o novo sistema de modularidade nem existissem, pois sua utilização é completamente transparerente.

Isto mostra que o projeto alcançou um ótimo consenso, consolidando um passo na direção de uma plataforma aderente a boas práticas do projeto de software; compatível com arquiteturas de software mais modernas (microserviços -- microservices); de distribuição melhorada (compartimentos -- containers); e otimizada, pois o novo JRE mínimo tem pouco mais de 15MB.

Na próxima parte deste post será tratada a implementação do Jigsaw na plataforma, ou seja, como estão organizados os módulos a partir da versão 9. A parte III inclui um exemplo de como construir uma aplicação modular.

Para Saber Mais

domingo, 10 de dezembro de 2017

Java 9::Melhorias no Optional

NullPointerException: quem nunca ficou realmente chateado ao ver, inesperadamente, esta exceção que atire a primeira crítica! Como toda exceção não prevista, é provável que sua ocorrência cause o encerramento anormal e abrupto do programa, algo muito constrangedor quando é o usuário que presencia este fato.
É notório que a famigerada exceção NullPointerException é lançada quando ocorre uma tentativa de acessar um campo ou acionar um método por meio de uma variável de referência inadequadamente inicializada, isto é, sem exista uma instância de objeto na variável referência utilizada. Traduzindo em miúdos: a variável de referência contém apenas null, que sinaliza a ausência de um objeto.

Todo programador sabe que isso (a tentativa de usar um objeto inexistente) é uma operação ilegal, ou seja, a questão aqui não é o uso inadvertido de variáveis de referência não inicializadas, até mesmo porque muitos dos IDE disponíveis, como o Eclipse, sinalizam, até com certo estardalhaço, que variáveis não inicializadas estão em uso; mas quando operações realizadas sobre outros objetos retornam referências válidas.

O problema mais comum, que dá origem às ocorrências de NullPointerException, é o acionamento de um método que, eventualmente, possa retornar um resultado null. Até a versão 7 do Java, a solução para evitar esta exceção era testar as variáveis de referências duvidosas, fazendo seu uso apenas quando diferentes de null. Mas isto polui o código.

A versão 8 do Java trouxe a classe Optional para solucionar este problemas.

Optional<T>

Com nítida inspiração nas linguagens de programação Scala, Haskell e Guava, a classe genérica Optional<T> funciona como uma classe genérica (um template portanto) para encapsular referências do tipo T que podem ser nulas. Seu uso permite que o projetista de uma API possa, mais claramente, indicar que um valor nulo pode ser retornando ou passado para um método, reduzindo a necessidade de consulta à documentação desta operação.

Objetos do tipo Optional<T> são como contêineres (i.e., containers) que podem armazenar um valor de qualquer tipo T ou apenas null. A classe Optional também provê alguns métodos úteis que podem eliminar a verificação explícita da presença de null.

Considere o trecho de código que segue:

String parametro = ????; // inicialização do parâmetro, que pode ou não ser nulo
System.out.println("Parâmetro está definido? " + (parametro!=null ? true : false) );
if (parametro == null) {
   System.out.println("Parâmetro: [sem definição]" );
} else {
   System.out.println("Parâmetro: " + parametro);
}
if (parametro != null) {
   System.out.println("programa " + parametro);
} else {
   System.out.println("programa");
}

Como a variável do tipo String denominada parametro pode ou não ser nula, para que não ocorram erros decorrentes de seu uso indevido, o trecho de programa verifica explicitamente (de maneiras diferentes) quando parametro é ou não nulo, avolumando o trecho e dificultando sua legibilidade. 

O uso da classe Optional<T> e seus métodos permite simplificar código como este, com uma construção que pode ser como segue, onde o parametro é nulo:

Optional<String> parametro = Optional.ofNullable(null);
System.out.println("Parâmetro está definido? " + parametro.isPresent() );
System.out.println("Parâmetro: " + parametro.orElseGet(() ‐> "[sem definição]") );
System.out.println(parametro.map(p ‐> "programa " + p).orElse( "programa" ) );

O método isPresent() retorna true quando a instância de Optional<T> contém um valor não nulo e false quando nulo. O método orElseGet() provê um mecanismo alternativo, baseado numa expressão lambda que se comporta como um Producer, que provê um valor quando Optional<T> contém nulo. Já o método map() transforma o valor corrente de Optional<T> e retorna uma nova instância. O método orElse() é semelhante a orElseGet(), mas toma um valor default ao invés de uma função de produção.

A saída deste programa, executado sem qualquer erro, é:

Parâmetro está definido? false
Parâmetro: [sem definição]
programa

Um outro exemplo seria o mesmo trecho de programa, mas com um valor provido para o parâmetro.

Optional<String> parametro = Optional.of("--check");
System.out.println("Parâmetro está definido? " + parametro.isPresent() );
System.out.println("Parâmetro: " + parametro.orElseGet(() ‐> "[sem definição]") );
System.out.println(parametro.map(p ‐> "programa " + p).orElse( "programa" ) );

Agora a saída deste programa, que continua sendo executado sem erros, é:

Parâmetro está definido? true
Parâmetro: --check
programa --check

Este exemplo simples permite observar que o uso do parâmetro, nulo ou não, é feito corretamente e de maneira segura, mesmo sem a presença de testes explícitos realizados com diretivas if.


Melhorias em Optional<T>

A versão 9 do Java traz algumas melhorias, pequenas, mas bastante úteis.

O novo método ifPresentOrElse() verifica se um valor está presente na instância de Optional<T>, conduzindo a ação indicada com o valor, ou realizando outra para a situação de vazio (conteúdo null):

public void ifPresentOrElse(Consumer<T> action, Runnable emptyAction);

Dito de outra maneira, este método codifica um padrão comum, no qual se deseja executar uma ação quando Optional<T> contém um valor, ou uma ação diferente quanto tal valor está ausente.

Considere o trecho que segue:

String parametro = getParameterByPosition(position);
if (parametro != null) {
   executeParameterizedAction(parametro);
} else {
   executeDefaultAction();
}

Com o uso do novo método de Optional<T>, o trecho equivalente seria:

Optional<String> parametro = Optional.of(getParameterByPosition(position));
parametro.ifPresenteOrElse(
    this::executeParameterizedAction,
    ()-> executeDefaultAction() );


Ou ainda mais simples, se o método getParameterByPosition(int) retornasse diretamente um Optional<String>:

getParameterByPosition(position).
   ifPresenteOrElse(this::executeParameterizedAction,
                    ()-> executeDefaultAction() );


Outro método interessante é or(), que toma uma função que cria um Optional<T> como argumento. Sua assinatura é:

public Optional<T> or(Supplier<Optional<T>> supplier);

Se o valor está presente, retorna um Optional<T> que encapsula tal valor, senão, retorna um outro Optional produzido pela função de geração. Isto é útil para encadear duas ou mais funções que retornam Optional, de modo que o primeiro Optional contendo um valor não nulo seja retornado.

Por exemplo:

public Optional<String> getRecord(int recordNumber) {
   return findInDatabase(recordNumber).or(()->findInFileSystem(recordNumber));
}

Este método retorna o resultado de findInDatabase(int), quando este não é nulo, ou ou resultado de findInFileSystem(int) quando não encontrado pelo primeiro método.

Conclusões

As melhorias em Optional<T> são simples, mas auxiliam na construção de programas melhores, principalmente livres da desagradável exceção NullPointerException. É claro que métodos cuja implementação ainda retorne null deverão ser modificados para retornar objetos Optional<T> ou, ao menos, ter seus resultados encapsulados em objetos Optional<T> para que tais benefícios possam ser percebidos. Por outro lado, a inclusão de Optional<T> no Java 8, e das melhorias comentadas no Java 9, não farão qualquer mágica, exigindo alguma manutenção, mas que com certeza, valerá o esforço.


Para Saber Mais


domingo, 26 de novembro de 2017

Java 9::Melhorias na Stream API

As streams para coleções e as operações em massa foram uma grande adição do Java 8, pois sua utilização permite a realização de operações complexas com código simples e bastante direto.
 
 
A característica mais marcante desta API é possibilitar que cada elemento de uma coleção seja tratado sem a necessidade de construir explicitamente laços de repetição para tal processamento, ou seja, permitindo aplicar as operações desejadas nas coleções como um todo, o que se denominou de operações em massa (bulk operations) sobre as coleções.

O Java 9 traz alguns complementos úteis para esta API, como novas fontes de dados para as streams, além de algumas novas características.

Novas fontes de dados

Todas as coleções possuem um método stream() que permitem iniciar um stream pipeline, o qual poderá ser utilizado posteriormente numa sequência arbitrária de operações intermediárias, até que seja realizada uma última operação terminal. A stream que dá origem ao stream pipeline é conhecida como stream fonte (source stream).

Além da possibilidade de criar um stream fonte a partir das coleções, existia no Java 8 um conjunto limitado de possibilidades obtenção de stream fonte a partir de elementos externos às coleções, como por exemplo, java.io.BufferedReader.lines().

O Java 9 adiciona algumas novas fontes úteis a esse conjunto, por meio destes novos métodos:
  • java.util.Scanner.tokens()
  • java.util.regex.Matcher.results()
  • java.util.Optional.stream()

Em particular o método tokens() da classe Scanner é bem versátil, como mostra o exemplo que segue:
 

Novas características das Streams

Existem quatro novos métodos adicionados na interface java.util.stream.Stream<T>:
  • Stream<T> takeWhile(Predicate)
  • Stream<T> dropWhile(Predicate)
  • Stream<T> ofNullable(T)
  • Stream<T> iterate(T, Predicate< T>, UnaryOperator<T>)

Apenas recordando, a interface Stream<T> representa uma sequência de elementos (na verdade, uma cadeia de referências de objetos) sobre os quais uma ou mais operações podem ser executadas. As subinterfaces IntStream, LongStream e DoubleStream são especializações de Stream<T> voltadas para os tipos primitivos mais comuns, contendo algumas funcionalidades adicionais.

Stream<T> takeWhile(Predicate) e Stream<T> dropWhilePredicate)

Dois dos novos métodos são takeWhile(Predicate) e dropWhile(Predicate), semelhantes aos métodos existentes limit(long) e skip(long), mas que tomam um predicado ao invés de valores fixos. Um predicado é uma condição arbitrada pelo programador, ou seja, um critério de seleção, que retorna true ou false.

O método takeWhile(Predicate) considera os elementos iniciais de um stream que atendem o predicado/critério dado.

Nos fragmentos que seguem (que podem ser facilmente testados com uso do jshell), é declarada e inicializada uma coleção com 1000 valores sequenciais (0, 0.33, 0.66, 1.0, 1.33 ...).
 
 

 
Esta coleção pode ser exibida integralmente com:
 
 
 

O método limit(long) permite obter uma nova stream com, por exemplo, os n primeiros elementos da coleção, no caso os cinco primeiros:
 
 
 
O novo método takeWhile(Predicate), ao invés de tomar um número fixo dos primeiros elementos como limit(long), toma todos os primeiros elementos que atendem o critério dado (o predicado fornecido). Assim takeWhile(Predicate) vai selecionando os elementos presentes no início da stream enquanto tal predicado é satisfeito. No fragmento que segue, este método é usado para extrair os primeiros elementos da coleção que são menores do que 2.5. 
 

O método skip(long), por sua vez, produz uma nova stream pulando os n primeiros elementos da coleção (no fragmento que segue n = 990), produzindo resultados ilustrados a seguir.
 
 
 
 
 

Já o novo método dropWhile(Predicate), descarta todos os primeiros elementos que atendem o critério dado (o predicado fornecido). Assim dropWhile(Predicate) vai pulando os elementos presentes no início da stream enquanto seu predicado é satisfeito. No fragmento que segue, este método é usado para descartar os primeiros elementos da coleção que são menores do que 330.
 
 
 
 

 
Como os valores da coleção estão ordenados, o resultado de takeWhile é bastante previsível. Mas, quando o conteúdo da coleção não está ordenado, o uso de takeWhile requer cautela, pois o primeiro valor que não atende o predicado interrompe a seleção de elementos, o que pode ocorrer no primeiro elemento existente, produzindo como resultado um stream vazio.

Observe o resultado do fragmento abaixo que gera uma coleção com conteúdo aleatório.
 
 
 
 

 
 
 
 
Conforme o critério usado com takeWhile, o resultado pode ser um stream com conteúdo ou mesmo vazio, apesar de existirem valores na coleção que atendem o critério, mas não seus primeiros. O exemplo que segue mostra esta situação.
 
 
 
 
O mesmo pode acontecer no uso de dropWhile.

Considerando que a seleção e o descarte proporcionados, respectivamente, por takeWhile e dropWhile, são condicionais (i.e., baseado em um predicado), pode ser conveniente ordenar o stream de onde os elementos serão retirados. A ordenação de um stream pode ser facilmente obtida com o método sorted(), que retorna um stream classificado conforme o critério de ordenação natural de seu conteúdo.
 

Stream<T> ofNullable(T)

Outro elemento novo é o método estático ofNullable(T) da interface Stream<T>. Esta operação retorna uma stream do tipo T contendo um elemento ou nenhum, nos casos do argumento fornecido ser não nulo ou nulo. Este método é bastante útil para eliminar a verificação de elementos nulos antes da construção de uma stream. O fragmento que segue ilustra o uso deste novo método.
 
 
 
 
 
 

Stream<T> iterate(T, Predicate< T>, UnaryOperator<T>)

Também foi adicionada uma nova versão do método estático iterate, que toma três argumentos: o primeiro é um valor de inicialização (seed), o segundo é uma condição expressa por um predicado, e o último é uma função de incremento ou decremento. O objetivo deste método é simular um laço, tal como:
for (T index=seed; hasNext.test(index); index = next.apply(index)) {
...
}
 
Conforme os elementos fornecidos, pode obter tanto uma sequência vazia, como uma contendo um número finito de elemento, de maneira bem conveniente. Nos fragmentos que seguem, esta versão de iterate é usada para gerar duas sequências de inteiros, uma transformada em array com toArray() e outra coletada na forma de uma lista com collect.
 
 
 
 

Conclusões

As novas adições efetuadas na Stream API tornam esta biblioteca ainda mais conveniente e flexível de usar, lembrando que quão versátil são as streams e suas operações de filtragem, mapeamento e redução. É algo que vale a pena estudar!

Para saber mais