BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Nashorn: Combinando o poder do Java e JavaScript no JDK 8

Nashorn: Combinando o poder do Java e JavaScript no JDK 8

Favoritos

Desde o JDK 6, o Java foi liberado contendo um motor de JavaScript com base no Rhino da Mozilla. Essa funcionalidade permite embarcar código JavaScript dentro do Java e até chamar no Java o código JavaScript embarcado. Além disso, ele permite a execução de JavaScript através de linha de comando usando o jrunscript. Isso é bom o suficiente quando não há necessidade de muito desempenho e é possível conviver com um conjunto limitado das funcionalidades do ECMAScript 3.

Começando com o Nashorn do JDK 8 atualizando o Rhino como motor embarcado de JavaScript no Java. O Nashorn fornece suporte completo para a especificação do ECMAScript 5.1 com mais algumas extensões. Permite compilar o JavaScript em bytecode Java usando as novas funcionalidades da linguagem com base na JSR 292, incluindo o invokedynamic, que foi introduzido no JDK 7.

Isso fornece um desempenho de 2 a 10x a mais que a antiga implementação do Rhino, apesar de ter o desempenho um pouco inferior ao V8, o motor dentro do Chrome e Node.js. Se estiver interessado nos detalhes da implementação dê uma olhada nesses slides do JVM Language Summit de 2013.

Como o Nashorn vem com o JDK 8, ele dá suporte às interfaces funcionais, como veremos em mais detalhes adiante.

Vamos começar com um exemplo bem simples. Primeiro será necessário instalar o JDK 8 e o NetBeans, IntelliJ IDEA ou Eclipse. Essas IDEs fornecem ao menos o suporte básico de JavaScript integrado com o desenvolvimento. Vamos criar um simples projeto Java contendo dois arquivos de exemplo e em seguida executaremos o programa, como mostrado na figura a seguir:

(Clique na figura para ampliar)

Na linha 12 usamos o método "eval" para avaliar qualquer código JavaScript. Nesse caso apenas carregamos o arquivo JavaScript principal e o avaliamos. Não se preocupe com o "print" no código, ele pode não parecer familiar porque não construímos essa função no JavaScript, mas o Nashorn fornece essa e outras funções por conveniência, que são convenientes no ambiente de scripting. Também seria possível passar o comando de impressão do "hello world" direto como argumento para o "eval", mas ter o JavaScript em seu próprio arquivo abre todo um mundo de ferramental para ele.

Atualmente o Eclipse não possui um suporte dedicado para o Nashorn através do projeto da Ferramenta de Desenvolvimento de JavaScript (JSDT), no entanto suporta o ferramental básico e a edição de JavaScript.

(Clique na figura para ampliar)

O IntelliJ IDEA 13.1 (edição da comunidade e final) fornece excelente suporte ao JavaScript e Nashorn. Há funcionalidades completas para depurar e também permitir o refatoramento que sincroniza o Java com o JavaScript; então por exemplo, se renomear uma classe Java que é referenciada no JavaScript ou se renomear um arquivo JavaScript que é usado no Java, a IDE modificará as referências correspondentes entre as linguagens.

A seguir temos um exemplo que permite depurar uma chamada ao JavaScript através do Java (note que o NetBeans também fornece depuração de JavaScript como mostrado na figura a seguir):

(Clique na figura para ampliar)

Pode-se dizer que o ferramental é bom e que a nova implementação corrige os problemas de desempenho bem como o problema de estar em conformidade com o JavaScript, mas por que devemos usa-lo? Uma razão pode ser o script em geral. Algumas vezes pode ser útil para tratar alguns tipos de strings e deixar apenas que seja interpretada. Algumas vezes pode ser bom não ter um compilador no caminho ou não se preocupar com tipos estáticos, ou talvez o interesse no modelo de programação do Node.js, que pode ser usado no Java como veremos no final desse artigo. Há também o caso do desenvolvimento no JavaFX que será muito mais rápido usando JavaScript do que apenas Java.

Shell scripts

O motor do Nashorn pode ser executado através da linha de comando usando o comando jjs. Pode executa-lo sem argumentos para abrir o modo interativo, ou pode informar o nome do arquivo JavaScript que deseja executar, ou pode usar para substituir o shell script, exemplo:

#!/usr/bin/env jjs

var name = $ARG[0];
print(name ? "Hello, ${name}!" : "Hello, world!");

Para passar argumentos no programa através do jjs, utilize o prefixo "--". Dessa forma podemos ter uma chamada como a seguinte:

./hello-script.js -- Joe

Sem o prefixo "--", o parâmetro será interpretado como o nome do arquivo.

Passando dados para o Java e vice-versa

Como mencionado anteriormente, é possível chamar o JavaScript direto do código Java através do motor chamado "ScriptEngine" e chamando seu método "eval". É possível passar dados explicitamente como strings ...

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
String name = "Olli";
nashorn.eval("print(\'" + name + "\')");

… ou pode passar referências do Java que podem ser acessadas como variáveis globais dentro do motor de JavaScript:

int valueIn = 10;
SimpleBindings simpleBindings = new SimpleBindings();
simpleBindings.put("globalValue", valueIn);
nashorn.eval("print (globalValue)", simpleBindings);

O resultado da execução do JavaScript é devolvido através do método "eval" do motor:

Integer result = (Integer) nashorn.eval("1 + 2");
assert(result == 3);

Usando classes Java no Nashorn

Como mencionado anteriormente, uma das funcionalidades mais poderosas do Nashorn é o uso de classes do Java dentro do JavaScript. É possível tanto acessar as classes e criar instâncias como também criar subclasses delas, chamar membros estáticos e fazer virtualmente qualquer coisa que é possível diretamente no Java.

Como um exemplo, vamos dar uma olhada em threads. O JavaScript não tem nenhuma funcionalidade da linguagem para tratar concorrência e todos os ambientes de execução comuns são de processos únicos ou pelo menos sem qualquer compartilhamento de estado. É interessante ver que no ambiente do Nashorn o JavaScript pode de fato ser executado concorrentemente e com compartilhamento de estado, tal como no Java:

// é assim que obtemos acesso à classe Thread do Java.
var Thread = Java.type("java.lang.Thread");

// cria a subclasse com o método run.
var MyThread = Java.extend(Thread, {
   run: function() {
       print("Run in separate thread");
   }
});
var th = new MyThread();
th.start();
th.join();

Note que a maneira canônica de acessar uma classe através do Nashorn é usando o Java.type e estendendo uma classe através do Java.exend.

Linguagem funcional

Por todos os aspectos, com o lançamento do JDK 8, o Java tornou-se - pelo menos até certo ponto - uma linguagem funcional. Agora é possível usar funções de ordem superior nas coleções, por exemplo para iterar sobre um conjunto de elementos. Uma função de ordem superior é uma função que tem outra função como parâmetro e faz algo significativo com ela. Veja um exemplo em Java:

List<Integer> list = Arrays.asList(3, 4, 1, 2);
list.forEach(new Consumer() {
   @Override
   public void accept(Object o) {
       System.out.println(o);
    }
 });

Nesse exemplo, ao invés de iterar sobre os elementos usando um laço "externo" como fazemos tradicionalmente, agora passamos uma função "Consumer" para o método "forEach", uma operação de ordem superior realiza um "laço interno" e executa o método "accept" do Consumer, percorrendo cada elemento da coleção, um a um.

Como mencionado, a abordagem da linguagem funcional para tal função de ordem superior aceitaria um parâmetro de função, em vez de um objeto. A passagem como referências para as funções em si, não é algo tradicional fornecido no Java, o JDK 8 agora possui alguns tipos sintáticos para expressar justamente o uso de expressões lambda (também conhecidas como "closures"). Por exemplo:

List<Integer> list = Arrays.asList(3, 4, 1, 2);
list.forEach(el -> System.out.println(el));

Nesse caso o parâmetro para o "forEach" tem o formato de uma referência de função. Isso é possível porque o Consumer é uma interface funcional (algumas vezes chamado de tipo de Método Abstrato Simples, ou do inglês "Single Abstract Method - SAM").

Então, porque abordamos lambdas na discussão sobre Nashorn? Porque no JavaScript podemos escrever códigos como esse e o Nashorn está especialmente preparado para fazer a ligação entre o Java e o JavaScript nesse caso. Em particular, porque permite passar funções escritas em JavaScript como implentações de interfaces funcionais (tipo SAM).

Vejamos alguns simples exemplos de código JavaScript que fazem coisas iguais as feitas com código Java. Note que não há uma forma de construção de listas no JavaScript, apenas arrays; mas esses arrays são dimensionados dinamicamente e tem métodos para comparação deles com uma lista em Java. Então, nesse exemplo estamos chamando o método "forEach" de um array em JavaScript:

var jsArray = [4,1,3,2];
jsArray.forEach(function(el) { print(el) } );

A similaridade é obvia; mas isso não é tudo. Podemos também converter um array em JavaScript para uma lista em Java:

var list = java.util.Arrays.asList(jsArray);

Esse código JavaScript executa dentro do Nashorn. Como isso agora é uma lista do Java, podemos chamar o método "forEach". Note que esse não é o mesmo método "forEach" que chamamos no array em JavaScript, mas sim o método "forEach" definido nas collections. Ainda, podemos passar uma função simples em JavaScript:

list.forEach(function(el) { print(el) } );

O Nashorn permite que passemos funções em JavaScript como referência, nos locais que esperam interfaces funcionais (tipo SAM). Portanto, isso não é possível apenas no Java, mas também no JavaScript.

A próxima versão do ECMAScript - que espera-se a versão final para esse ano - incluirá uma sintaxe curta para funções que permitirá códigos similares aos lambdas em Java, exceto que no JavaScript será usado seta dupla =>. Isso facilitará ainda mais o alinhamento entre as linguagens.

Dialeto especial do JavaScript no Nashorn

O Nashorn suporta atualmente a versão 5.1 do ECMAScript e mais algumas extensões. Não recomendo o uso dessas extensões porque não se parecem com Java e nem JavaScript, parece algo não natural para os desenvolvedores. No entanto há duas extensões que são usadas na documentação da Oracle e podemos nos familiar com elas.

Primeiro vamos configurar o cenário para a primeira extensão. Como visto anteriormente podemos estender classes Java através do JavaScript usando o Java.extend. Se quiser criar uma subclasse de uma classe Java abstrata ou implementar um interface podemos usar uma sintaxe mais conveniente. Nesse caso é possível chamar virtualmente o construtor de uma classe abstrata ou interface e passar um objeto literal JavaScript que descreve os métodos implementados. As literais do objeto JavaScript são apenas pares de nome e valor, muito parecido com o formato JSON. Nesse exemplo foi implementado a interface Runnable:

var r = new java.lang.Runnable({
   run: function() {
       print("running...\n");
   }
});

Nesse exemplo estamos virtualmente chamando o construtor de Runnable com um objeto literal que especifica a implementação do método run. Note que, isso é algo que a implementação do Nashorn possui, em outros casos isso não seria possível apenas com JavaScript.

O código desse exemplo é bem parecido com a implementação de uma interface usando uma classe interna anônima em Java. Isso nos leva a primeira extensão, que deixa passar o último parâmetro após o parênteses ")" de fechamento durante a chamada do construtor. Fazendo isso, o código ficaria como esse ...

var r = new java.lang.Runnable() {
   run: function() {
      print("running...\n");
   }
};

… que faz exatamente a mesma coisa, mas tem uma semelhança ainda maior com o Java.

A segunda extensão mais usada é um atalho para as funções que permite omitir ambas as chaves bem como a instrução de retorno, para que o corpo do método possa ser em uma única linha. Assim, o exemplo da sessão anterior ...

list.forEach(function(el) { print(el) } );

… pode ser expresso de forma ligeiramente mais conciso:

list.forEach(function(el) print(el));

Avatar.js

Vimos que com o Nashorn temos um bom motor de JavaScript embarcado no Java. Também vimos que o Nashorn pode acessar as classes do Java. O Avatar.js vai um passo além e traz "o modelo de programação do Node, APIs e os módulos do ecossistema para a plataforma Java". Para entender o que isso significa e porque é excitante, primeiro vamos entender o que é o Node.

O Node é basicamente uma extração do motor de JavaScript V8 do Chrome que pode ser executado através de linhas de comando, sem precisar de um navegador. Isso permite que o JavaScript seja executado não apenas no navegador, mas também no lado servidor. Para executar o JavaScript no servidor de uma forma significativa, será necessário acessar pelo menos o sistema de arquivos e a rede. Para fazer isso, o Node possui uma biblioteca chamada libuv que faz isso de maneira assíncrona. Na prática, isso significa que as chamadas ao sistema operacional não ficam bloqueadas mesmo que demorem algum tempo para responder. Ao invés de bloquear, é fornecida uma função de callback que será acionada uma vez que a chamada terminar, entregando os resultados, se houver algum.

Há diversas empresas que usam o Node para aplicações sérias, como o Walmart e o Paypal.

Vejamos um pequeno exemplo de JavaScript que foi adaptado do site do Node:

// carrega o módulo 'http' (isso é bloqueante) para tratar requisições http.
 var http = require('http');

// quando há uma requisição, retornamos 'Hello, World\n'.
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello, World\n');
 }

// Vamos escutar a porta 1337 do localhost
// e dar um handleRequest quando a chamada voltar.
// Veja aqui a forma sem bloqueio / assíncrona.
http.createServer(handleRequest).listen(1337, '127.0.0.1');

// registramos no console para garantir que a aplicação foi inicializada.
console.log('Get your hello at http://127.0.0.1:1337/');

Para executar esse código será necessário instalar o Node, salvar o códigos JavaScript no arquivo e finalmente chamar o Node passando o arquivo como parâmetro.

O objetivo do Avatar.js é fornecer a mesma API central como o Node ligando o libuv com as classes Java e então torná-lo acessível ao JavaScript. Mesmo que isso possa parecer complicado, funciona surpreendemente bem. O Avatar.js suporta diversos módulos Node e suporta o "express" - o principal framework web para Node - indicando que isso pode funcionar com diversos projetos existentes.

O Avatar.js possui uma distribuição binária que pode ser encontrada no site do projeto bem como os passos para a sua instalação.

Um vez que tenha configurado os binários e colocados na pasta lib, podemos então chamar o framework Avatar.js usando algo como:

java -Djava.library.path=lib -jar lib/avatar-js.jar helloWorld.js

Assumimos que no servidor de demonstração está salvo um arquivo chamado "helloWorld.js".

Novamente perguntamos, porque isso é útil? As pessoas na Oracle (slide 10) viram diversos casos de uso para uma biblioteca. Concordo principalmente com dois deles:

  1. Se temos uma aplicação Node e queremos usar certas bibliotecas do Java para complementar a API do Node;
  2. Se queremos trocar para as APIs do JavaScript e Node, mas precisamos embarcar o código Java legado em partes ou completamente.

Ambos casos de uso funcionam usando o Avatar.js e chamando qualquer classe necessária do Java através do código JavaScript, que é suportado pelo Nashorn.

Vejamos um exemplo do primeiro uso. O JavaScript tem atualmente apenas um único tipo para expressar números chamado "number". Sendo equivalente ao tipo de precisão do "double" do Java, com as mesmas limitações; o number do JavaScript, como o double do Java não é capaz de expressar tamanho e precisão arbitrária, por exemplo para tratar de valores monetários.

No Java podemos usar o BigDecimal, que suporta exatamente isso. Mas o JavaScript não tem uma construção equivalente, então podemos acessar a classe BigDecimal através do código JavaScript e ter o mesmo tratamento seguro de valores monetários.

Vejamos um exemplo de web service que calcula o percentual de alguma quantia. Primeiro precisamos de uma função que faz o cálculo real:

var BigDecimal = Java.type('java.math.BigDecimal');

function calculatePercentage(amount, percentage) {
   var result = new BigDecimal(amount).multiply(
    new BigDecimal(percentage)).divide(
          new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
   return result.toPlainString();
}

No JavaScript não há declaração de tipos, mas fora isso, o código se parece muito com o código Java, como mostrado a seguir:

public static String calculate(String amount, String percentage) {
   BigDecimal result = new BigDecimal(amount).multiply
    new BigDecimal(percentage)).divide(
         new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
   return result.toPlainString();
}

Apenas precisamos substituir a função handleRequest do Node, como no exemplo anterior, para completarmos o código. Como esse:

// Carrega o módulo utilitário 'url' para converter a url.
var url = require('url');

function handleRequest(req, res) {
  // '/calculate' é o caminho do web service.
   if (url.parse(req.url).pathname === '/calculate') {
       var query = url.parse(req.url, true).query;
      // Quantia e percentual são passados como parâmetros de consulta da requisição.
       var result = calculatePercentage(query.amount,
                                         query.percentage);
       res.writeHead(200, {'Content-Type': 'text/plain'});
       res.end(result + '\n');
   }
}

Usamos o segundo módulo principal do Node para processar a URL da requisição para converter os parâmetros da consulta em quantia e porcentagem.

Após iniciar o servidor, se fizermos uma requisição como ...

http://localhost:1337/calculate?amount=99700000000000000086958613/percentage=7.59

… usando o navegador web, obtemos a resposta correta "7567230000000000006600158.73" que seria impossível de calcular usando apenas o tipo "number" do JavaScript.

O segundo caso pode fazer mais sentido quando decidimos migrar uma aplicação JEE existente para JavaScript e Node. Nesse caso podemos facilmente acessar todos os serviços existentes com o JavaScript. Outro caso de uso relacionado, podemos ter uma nova parte da funcionalidade do servidor construído usando JavaScript e Node que pode se beneficiar dos serviços JEE existentes.

Indo na mesma direção, há também o Projeto Avatar que é baseado no Avatar.js. Os detalhes vão além do escopo desse artigo, mas para ter uma visão rápida dê uma olhada no anúncio feito pela Oracle. A ideia básica é escrever a aplicação em JavaScript e acessar os serviços JEE. O Projeto Avatar vem com uma distribuição binária combinanda com o Avatar.js mas precisa do Glassfish para instalar e desenvolver.

Resumindo

O projeto Nashorn aumentou a implementação original do Rhino no JDK 6 fornecendo melhorias de desempenho para aplicações de execução longa, por exemplo quando usamos dentro de um servidor de aplicações web. O Nashorn integra o Java com o JavaScript e permite usar as novas expressões lambdas do JDK 8. Uma inovação real vem com o Avatar.js que fornece integração com o Java EE e o código JavaScript sendo amplamente compatível com o padrão para programação no servidor JavaScript.

Os exemplos completos, incluindo o binário do Avatar.js para Mac OS X, estão disponíveis no Github.

Sobre o autor

Oliver Zeigermann é consultor de arquitetura e desenvolvimento de software, e Coach em Hamburg, Alemanha. Atualmente tem se concentrado em usar JavaScript em aplicações corporativas.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT