Neste artigo veremos de forma prática desde os conceitos mais básicos de instalação e configuração, até a comunicação do Nashorn com APIs como o JavaFX e Avatar.js, de forma a passar uma ideia do poder e capacidade dessa tecnologia.
Você provavelmente já leu ou ouviu falar sobre algumas das principais características da última versão do Java SE 8 e JDK 8, tais como:
- Projeto Lambda (JSR 335), o que torna mais fácil escrever código para processadores multi-core,
- Um conjunto de perfis compactos, que permitem implementações do Java SE 8 de baixa escalabilidade com facilidade,
- Uma nova API de data e hora (JSR 310),
- E a remoção da “geração permanente” do JVM HotSpot.
Todos esses recursos incorporaram às novas versões da linguagem grandes esforços de engenharia, implementação e praticidade no que concerne ao desenvolvimento de softwares como até então havia sendo feito.
O projeto Nashorn, outra característica fundamental do JDK 8, não é uma exceção: ele introduz uma nova implementação mais leve e de alto desempenho do JavaScript e integra-a ao JDK.
Mas antes que você comece a se perguntar sobre toda essa confusão de ter duas expressões até então tidas como bem distintas e diferentemente localizadas: JavaScript (no lado cliente das aplicações) e Java (no lado servidor), voltemos um pouco a linha do tempo para entender como tudo começou e qual o objetivo de se usar as duas linguagens em conjunto.
JavaScript na JVM
No princípio tínhamos a JSR 223: Scripting for the Java platform. Essa JSR foi a principal responsável pela criação do pacote que viria a ser o precursor de toda inclusão JavaScript dentro do Java: o javax.script (também podendo ser chamado de API JavaScript). Essa API javax.script fornece uma linguagem de script independente para usar tais linguagens diretamente do Java. Ao mesmo tempo essa API somente foi incluída pela primeira vez na versão da plataforma Java SE 6, tendo como empacotamento principal o mecanismo de scripts baseado no Mozilla Rhino para JavaScript.
Mozilla Rhino é uma implementação open source do JavaScript baseada na linguagem de programação Java. Ele leva o nome de “rinoceronte” em alusão a um popular livro de desenvolvimento em JavaScript. Ao longo dos anos, no entanto, seu desempenho caiu muito frente a outros engines JavaScript. Para melhorar o desempenho, o Rhino precisaria ser reescrito para explorar plenamente as capacidades da plataforma Java moderna.
Mas o porquê então de usar JavaScript junto com Java? Bem, o principal beneficio de se fazer isso seria solucionar um problema extremamente comum no mundo das aplicações web: a validação das regras de negócio.
Imagine a seguinte situação: você está desenvolvendo um aplicativo baseado na web para uma finalidade qualquer, e aí você se depara com o comum desafio de escrever código para efetuar as validações da lógica de negócios. A maioria dos frameworks web apoia a noção de validações em nível de campo para assegurar, por exemplo, que um campo de texto que recebe o valor da idade de uma pessoa permita apenas números inteiros positivos entre 0 e 120, ou que um endereço de e-mail siga o padrão de usuario@dominio.dominiotoplevel. Mas o que frameworks web têm dificuldade em validar são as suas regras de negócio.
Para solucionar esse tipo de situação as ações comuns de muitos desenvolvedores geralmente são:
- Cliente: não é interessante que os usuários tenham que preencher um formulário inteiro antes de notificar-lhes que um campo é inválido, então no lado do cliente, usa-se JavaScript para validar os valores de campo individuais;
- Servidor todas as alegações finais para o servidor devem ser validadas usando um engine de regras; este pode ser feito em Java mesmo, ou em linguagens semelhantes como Groovy ou Scala.
Um dos objetivos de qualquer aplicação web ante um negócio específico é fornecer aos usuários um feedback imediato sobre os dados que estão entrando, assegurando também a integridade das regras de negócio no servidor. Mas isso cria um desafio, ou seja, temos de escrever muitas das regras de negócio duas vezes: uma vez no lado do cliente e outra no do servidor. O desafio é ainda agravado pela manutenção. Por exemplo, usando a mesma situação apresentada, imagine o que aconteceria se o negócio decidisse que a faixa de idade válida passará a ser entre 4 e 130? A resposta é que teríamos de atualizar o JavaScript na página, bem como as regras no servidor back-end.
Diante desse cenário é onde entra o Nashorn (assim como o Rhino, antigamente), pois ele permite a execução de uma regra só em ambas as camadas.
Adicionalmente, é possível encontrar JavaScript no lado servidor em outras situações. Por exemplo, o Node.js é usado para construir servidores leves e rápidos baseado no engine V8 JavaScript do Google Chrome. Engines JavaScript em navegadores da Web têm acesso ao modelo de objeto de documento HTML (DOM) e podem manipular elementos HTML através do DOM. Dado que diferentes navegadores Web têm diferentes DOM e engines de JavaScript, frameworks como jQuery tentam esconder os detalhes de implementação do programador.
Visão geral do Nashorn
Nashorn, pronuncia-se “nass-horn”, em alemão significa “rinoceronte”, e é um dos nomes de animais para um contratorpedeiro de tanque alemão usado na Segunda Guerra Mundial. É também o nome do substituto - introduzido com o novo Java 8 - para a velha e lenta engine Rhino JavaScript, conforme falamos na seção anterior. Ambos Rhino e Nashorn são implementações da linguagem JavaScript escritos para executar na máquina virtual Java.
Ele nasceu da iniciativa de um projeto no OpenJDK Community, em 2012, e por ter o código que compõe a implementação todo open source, atraiu adotantes e melhorou rapidamente, a ponto de passar por todos os testes de conformidade ECMAScript. Diante disso, a tecnologia foi escolhida para compor a próxima versão do JDK 8 e evoluiu para uma das principais características desse lançamento.
O JavaScript, também, tem evoluído nos últimos anos, e é possível observar o seu uso fora do nicho original, dentro de navegadores web. Isso pode fazer uma implementação totalmente compatível, com alto desempenho de JavaScript na JVM cada vez mais atraente para os desenvolvedores Java e JavaScript.
JavaScript pode ter Java como parte do seu nome, mas as duas línguas são muito diferentes em espírito e design, bem como em suas implementações. No entanto, uma forma de implementar um interpretador de JavaScript é compilar JavaScript em bytecode, que é o que o Rhino e o Nashorn foram projetados para fazer.
Nashorn, e Rhino, antes disso, explicitamente não suportam o DOM do navegador. Implementados na JVM, eles normalmente são chamados para scripting do usuário final em aplicações Java. Nashorn e Rhino podem ser incorporados em programas Java e usados como linhas de comando shell. Claro, o esforço adicional necessário quando você está criando scripts Java a partir do JavaScript preenche a lacuna entre os dados e tipos de incompatibilidade das duas linguagens.
Recursos e Melhorias
Jim Laskey, consultor membro da equipe técnica da Oracle, descreveu os objetivos do Nashorn da seguinte forma:
- Será baseado na especificação da linguagem ECMAScript-262 Edition 5.1 e deve passar por todos os testes de conformidade da ECMAScript-262.
- Apoiará a API javax.script (JSR 223).
- Será concedido apoio para invocar código Java a partir de JavaScript e Java para invocar código JavaScript. Isso inclui mapeamento direto para JavaBeans.
- Irá definir uma nova ferramenta de linha de comando, JJS, para avaliar o código JavaScript em scripts shell.
- O desempenho e uso de memória de aplicações Nashorn devem ser significativamente melhores do que do Rhino.
- Não vai expor quaisquer riscos de segurança adicionais.
- As bibliotecas fornecidas devem funcionar corretamente sob localização.
- Mensagens de erro e documentação serão internacionalizadas.
Laskey também limita explicitamente o escopo do projeto com alguns “não objetivos”:
- Só apoiará ECMAScript-262 Edição 5.1. Não vai apoiar quaisquer características da Edição 6 ou quaisquer recursos não padronizados fornecidos por outras implementações de JavaScript.
- Não irá incluir um plug-in API para browser.
- Não incluirá suporte para DOM/CSS ou quaisquer bibliotecas relacionadas (tais como jQuery, Prototype, ou Dojo).
- Não incluirá suporte a debugging direto.
Então, o que significa dizer que será baseado na edição 5.1 do ECMAScript-262? O diferencial aqui é que o Rhino foi baseado numa especificação mais antiga, na Edição 3.
A falta de suporte de depuração no Nashorn é um passo para trás a partir do Rhino, que tem seu próprio depurador JavaScript. No entanto, você vai encontrar soluções alternativas para essa omissão deliberada em pelo menos duas IDEs populares.
Observação: Nashorn é a única engine JavaScript incluída no JDK. No entanto, você pode usar qualquer mecanismo de script compatível com a JSR 223, ou implementar o seu próprio.
Interoperabilidade
Em vez de ser apenas mais uma engine JavaScript, o Nashorn fornece interoperabilidade entre os mundos Java e JavaScript. Isso significa que seu código Java pode chamar o código JavaScript, e vice-versa.
O Nashorn fornece objetos globais para acessar e instanciar classes Java a partir de JavaScript. Seus membros podem ser acessados usando a familiar notação '.' como no Java. Getters e setters nos JavaBeans são expostos como propriedades do JavaScript equivalentes. Arrays Java podem ser instanciados e acessados a partir do JavaScript, enquanto arrays de JavaScript podem ser convertidos em arrays Java comuns, quando necessário. Você pode usar “for” e “for each” para percorrer os dois tipos de arrays. Strings e números são tratados de forma transparente, interpretando-os como instâncias das classes Java correspondentes, dependendo da operação realizada. Finalmente, as coleções são interpretadas como arrays também.
Além disso, você pode estender as classes Java, fornecendo uma função de JavaScript que implementa um novo método. O Nashorn pode estender automaticamente métodos de classes simples e abstratas se você fornecer a implementação do novo método no construtor. Isto leva a um código muito compacto para action listeners, por exemplo.
Shell Scripting
O Nashorn vem com uma série de pequenas extensões para torná-lo mais fácil de usar JavaScript no shell. Elas são ativadas passando a flag -scripting para a aplicação jjs. Mas se o seu script começar com os caracteres (#!) e o caminho direto para a aplicação jjs, você não precisa passar a mesma.
Você pode usar interpolação de string (${var}) para usar os valores das variáveis ou expressões para a construção de strings dentro de aspas duplas. Você também pode usar um here document (heredoc) para especificar strings que preservam as quebras de linha e recuo. As variáveis de ambiente, os argumentos de linha de comando, o output e strings de erro estão disponíveis como objetos globais, bem como o código de saída do script e uma função global para executar comandos.
Além disso, o Nashorn fornece várias funções internas para sair do script, imprimir e ler as linhas de saída padrão e de entrada, ler arquivos, scripts de carga, e as propriedades de vinculação de objetos.
Performance e bytecodes
Por detrás dos panos, o Nashorn usa a instrução JVM invokedynamic para implementar todas as suas invocações. Esse é um componente importante na melhoria comparativa do desempenho e uso de memória sobre o Mozilla Rhino. Ao contrário do Java ou Scala, cujos compiladores são persistentes (ou seja, geram arquivos de classe/jar para o disco), o Nashorn compila tudo na memória e passa o bytecode para a JVM diretamente.
Uma vez que o JavaScript não tem um formato de bytecode “nativo”, o código fonte JavaScript é analisado em primeiro lugar para a construção de uma representação imediata (AST/IR). A AST/IR é então reduzida para algo mais próximo do bytecode JVM pela transformação de controles, redução de expressões para operações primitivas, e a simplificação das chamadas, a ser traduzido de forma eficiente com as instruções da JVM, e seguramente carregado na mesma. O código gerado e o histórico de chamadas são armazenados em cache pelo linker, para fazer lookup e invocação mais rapidamente em relinks sucessivos - JavaScript sendo uma linguagem dinâmica, o código real que precisa ser executado pela JVM para uma função que está sendo chamado em um determinado ponto no código pode mudar ao longo do tempo, e precisa ser recompilado e reconectado (relinked). O Nashorn cuida de tudo implicitamente através de bibliotecas auxiliares de alta qualidade provindas de terceiros.
O ganho em performance é tamanho que uma análise mais a fundo no desempenho comparado entre as tecnologias do Rhino e o do próprio Nashorn, pode ser averiguado através da Figura 1.
Command-line: instalando o JSS e o jrunscript
Uma vez que você instalar um JDK 8, ou construir sua própria instalação a partir do código-fonte do OpenJDK, você será capaz de usar a engine Nashorn através da API javax.script e a ferramenta jrunscript, assim como usaria com o Rhino. Além disso, o Nashorn vem com um novo aplicativo de linha de comando chamado jjs, que está localizado no diretório bin da instalação do seu JDK 8. A ferramenta jjs pode ser usada para executar e avaliar programas de JavaScript diretamente, tornando-o fácil de usar JavaScript para escrever shell scripts, código de protótipo na linha de comando, e até mesmo escrever aplicações JavaFX em JavaScript.
A primeira coisa que precisa fazer é verificar se o seu Sistema Operacional reconhece o JDK 8 como padrão na sua máquina, para tal acesse o prompt de comando cmd e digite o comando:
java –version
Neste artigo usaremos como padrão as configurações relacionadas ao Sistema Operacional Windows 7. Mas as mesmas podem ser aplicadas em semelhança aos demais SOs e respectivas versões.
Se o resultado for equivalente ao exibido na Listagem 1, então você já tem tudo que precisa para trabalhar com o Nashorn. Caso contrário, instale a versão correta do JDK e refaça estes passos. Desconsidere os “updates” que são os números que vem após o numero da versão em si, 1.8.0_05, por exemplo.
java version “1.8.0” Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
Após isso você estará apto a executar por default a ferramenta jrunscript, que já vem acoplada à instalação comum do JDK. Digite o seguinte comando a seguir para executar o bat do jrunscript e testar um alert JavaScript, ao mesmo tempo:
jrunscript js> alert(“Alo Mundo, Devmedia!”);
O resultado será semelhante ao representado pela Figura 2.
Esse resultado serve para explicitar e lembrar ao leitor acerca da importância de considerar que a execução de scripts via geração de bytecodes não leva em conta a existência de um DOM ou sequer objetos de browser. A função de alerta “alert()” faz uso desses recursos ao solicitar ao browser exibir graficamente uma mensagem passada por parâmetro.
Note também que o estilo de mensagens de erro no mesmo se equipara ao do Java, uma vez que estamos executando código bytecode no fim das contas. A presença da classe EcmaError também reforça a ideia de padronização JavaScript.
No entanto, se tentarmos a execução de um código diferente, porém reconhecido por ambas as linguagens, como o método print, então teríamos um resultado com sucesso:
js> print(“Alo Mundo, Devmedia!”); Alo Mundo, Devmedia!
Outros tipos de operações básicas como a associação de valores do tipo literal ou a adição de valores numéricos, bem como demais operações aritméticas são perfeitamente possíveis de serem executados através do jrunscript. Veja o caso representado pela Listagem 2. Nele você pode conferir a execução de todos esses casos e o resultado final pode ser verificado na Figura 3.
var a = 5; var b = 7; var c = 10; println(a + b); println(a + b + c); println(a+b/c); println(a*b/c); var d = “ABC”; println(a+d); println(a+b+c+d); quit();
Ainda no contexto do exemplo anterior, é possível trabalhar observando um dos conceitos mais complexos da implementação do Nashorn: a forte diferença de tipagens entre as duas linguagens. Através da função typeof, presente em ambas as linguagens, é possível observar o que o compilador considera como tipo básico para o valor em questão:
js> print(typeof(a+d)); string
Isso é nada mais que um elegante efeito colateral da tipificação fraca e sobrecarga do operador “+” em JavaScript. É o comportamento correto de acordo com a especificação JavaScript, e não um bug.
O Nashorn suporta o caractere “#” como um marcador principal de linha de comentário, assim tanto jjs como jrunscript podem ser usados em scripts escritos em JavaScript. Se estiver usando Mac ou Linux, você terá que marcar o arquivo JavaScript como executable com o utilitário chmod para torná-lo executável.
Mas e o jss, onde fica? Se você executar jjs na linha de comando do seu prompt, provavelmente receberá a seguinte mensagem de erro:
“'jjs' não é reconhecido como um comando interno ou externo, um programa operável ou um arquivo em lotes.”
Isso acontece porque o jjs não é configurado no diretório de binários padrão do Java quando da sua instalação. Para tal você precisaria acessá-lo diretamente. Geralmente, o caminho padrão de instalação (para Windows) é: C:\Program Files\Java\jdk1.8\bin, e lá você irá encontrar um arquivo bat chamado jjs.bat. Basta executá-lo ou acessar o mesmo através do seu próprio prompt. Veja na Figura 4 o resultado da execução do mesmo script da Listagem 2 para efeito de comparação entre ambas as ferramentas.
Observe que para esta execução retiramos a presença do método println(), substituindo-o pelo print(), uma vez que o jjs não o reconhece como função válida.
Adicionalmente, você pode habilitar um modo de scripting no jjs que não existe no jrunscript. No modo de criação de scripts, expressões dentro de aspas simples são passadas para a camada externa para a avaliação (Listagem 3).
jjs -scripting jjs> print ('alo'); Applications Applications (Parallels) Creative Cloud Files Desktop ... work jjs>
Chamando JavaScript a partir do Java
Para um primeiro momento desenvolveremos um projeto de teste, que nos auxiliará com a composição das listagens e respectivos testes no código, tanto Java quanto em JavaScript. Para tanto, você precisará da IDE Eclipse, em sua versão clássica, qualquer versão que já aceite a versão 8 do Java.
Após instalar o Eclipse, crie um novo projeto Java simples, aqui o chamaremos de “nashorn-devmedia”, e nele colocaremos todo o fonte que se refira aos testes com Nashorn que fizermos daqui pra frente.
Crie também uma classe e nomeie-a Teste, com um método main implementado para que possamos efetuar a chamada aos códigos JavaScript, tal como na Listagem 4.
package br.edu.devmedia.nashorn.alomundo; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class Teste { public static void main(String[] args) { new Teste().executarViaArquivoJS(); //new Teste().executarViaCodigoInline(); } private void executarViaArquivoJS() { try { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName(“nashorn”); engine.eval(“load(\”src/js_java_exemplo.js\”);”); } catch (ScriptException ex) { ex.printStackTrace(); } } private void executarViaCodigoInline() { try { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName(“nashorn”); engine.eval(“function ola() {\n var ola = 'DEVMEDIA'.toLowerCase();\n itera();\n print('Ola Mundo ' + ola + '!');\n}\nfunction itera() {\n var cont = 1;\n for (var i = 0, max = 5; i < max; i++) {\n cont++;\n }\n print('Valor da var cont: ' + cont);\n}\nola();”); } catch (ScriptException ex) { ex.printStackTrace(); } } }
Para chamar um script JavaScript a partir do Java via Nashorn você basicamente precisa implementar uma nova instância de ScriptEngineManager e usá-la para carregar o script pelo nome, através do método getEngineByName(). Este método existe para ajudar o Java a identificar que tipo de engine de scripts está prestes a ser usado, diante da possível existência de outros carregados no classpath das aplicações Java.
Em seguida, é possível verificar a chamada direta ao método “eval()” que se encarrega de avaliar a expressão JavaScript recebida em forma de parâmetro String e executar a mesma. A função desse método é basicamente a de executar o código JavaScript recebido como se simulasse um interpretador JavaScript de um browser comum.
Consequentemente, o entendimento do código da Listagem 4 não poderá ser totalmente compreendido sem a criação dos arquivos JavaScript correspondentes. Note que o método main faz a chamada a dois métodos internos privados que se caracterizam por demonstrar o dois possíveis meios de se carregar código JavaScript a partir do Java: via código inline, ou através da importação dos próprios arquivos JavaScript diretamente no código Java.
Dentro da pasta “src” do seu projeto Java, crie um novo arquivo JavaScript selecionando as opções “New > Other > JavaScript > JavaScript Source File” e chame-o de “js_java_exemplo.js”, para seguir o padrão de nomenclaturas adotado nas listagens destes exemplos. Insira no mesmo o código presente na Listagem 5.
function ola() { var ola = 'DEVMEDIA'.toLowerCase(); itera(); print('Ola Mundo ' + ola + '!'); } function itera() { var cont = 1; for (var i = 0, max = 5; i < max; i++) { cont++; } print('Valor da var cont: ' + cont); } ola();
Observe agora a comunicação entre o código contigo na Listagem 4, referente às chamadas do arquivo de mesmo nome criado agora na Listagem 5. Isso mostra a conformidade entre chamadas físicas ao arquivo tanto no lado servidor/Java quanto em um possível universo web, com a inclusão de chamadas pelo browser no lado cliente.
Basicamente temos duas funções no arquivo como um todo (totalmente código JavaScript): a primeira, a função ola(), se encarrega de criar uma nova variável com um valor literal todo em maiúsculo e logo em seguida chama a função “toLowerCase()” que será responsável por traduzir o texto para sua versão em minúsculo. Em seguida, chamamos a segunda função, itera(), que criará uma variável contadora e irá iterar seu valor até 6, imprimindo-o no final. Finalmente, a execução volta e imprime a mensagem de boas vindas formatada.
Interessante notar que toda essa execução findará no resultado abaixo, somente porque efetuamos a chamada ao método “ola()” no final do mesmo arquivo js:
Saída do Console: Valor da var cont: 6 Ola Mundo devmedia!
O mesmo resultado poderá ser verificado quando da execução do segundo método da classe Teste, executarViaCodigoInline(), uma vez que o mesmo contém a mesma linha de execução, porém portando todo o código inline. Quando a execução exigida for a presente no primeiro método, neste caso é obrigatório o uso do método JavaScript “load()” para que possa assim carregar o arquivo js apropriadamente.
Adicionalmente, veja que os métodos estão cercados pela cláusula try/catch em vista da possibilidade de haver alguma exceção do tipo ScriptException. Em nível de teste rápido, mude o nome do arquivo JavaScript no próprio ou apenas na chamada ao load. Você deverá receber uma exceção do tipo:
javax.script.ScriptException: TypeError: Cannot load script from src/java_js_exemplo2.js in <eval> at line number 1
Chamando Java a partir do JavaScript
Efetuar chamadas do Java a partir de arquivos JavaScript é tão fácil quanto o inverso. O único conceito a se assimilar (e se acostumar, pois realmente é bem incomum) é o de efetuar a chamada explícita dos códigos Java de dentro do arquivo js. Isso porque as classes das bibliotecas Java 8 estão construídas dentro do Nashorn. Crie um novo arquivo js também na pasta src e nomeie-o como “java_js_exemplo.js”. Considere a inclusão do código apresentado na Listagem 6 no mesmo.
// Recupera o tempo corrente em milissegundos print(java.lang.System.currentTimeMillis()); // Imprime valores absolutos do diretório JS var file = new java.io.File(“js_java_exemplo.js”); print(file.getAbsolutePath()); print(file.absolutePath);
Nesse código podemos observar duas implementações básicas: uma chamada à classe java.lang.System (Sim, precisamos importar sempre as classes Java que serão usadas) para impressão do valor atual do tempo em milissegundos, e uma chamada ao arquivo “js_java_exemplo.js” que se encontra no mesmo diretório relativo para impressão do seu caminho absoluto. Note que existem duas chamadas diferentes aos valores do “absolutePath”, demonstrando que não é necessário fazer chamadas explícitas aos métodos getters e setters dos objetos em JavaScript, isto é, o JavaScript converte automaticamente o atributo aos métodos get’s e set’s quando da conversão para bytecode.
Perceba também que o Nashorn não importa o pacote Java por padrão, porque as referências a String ou Object podem gerar conflito com os tipos correspondentes no JavaScript. Assim, uma String em Java é sempre representada por java.lang.String, e não somente String.
Para testar o exemplo anterior, basta mudar a chamada no método “executarViaArquivoJS()” para:
engine.eval(“load(\”src/js_java_exemplo.js\”);”);
Dessa forma, ao executar, teremos um resultado semelhante ao descrito a seguir sendo impresso no Console:
1402875392174 – O valor varia de acordo com a hora da execução C:\Users\Julio\nashorn-devmedia\js_java_exemplo.js C:\Users\Julio\nashorn-devmedia\js_java_exemplo.js
O ponto chave de toda a execução é levar em consideração que o meio comum entre as duas implementações está na função “print()”, reconhecida por ambas linguagens. Assim, é possível averiguar os resultados em ambos os lados.
As implementações podem ir além, exigindo mais poder de processamento, como a inclusão de conversores e formatadores, bem comuns à linguagem Java quando se considera as regras de negócio dos sistemas, bem como a necessidade das mesmas. Efetue a mudança do conteúdo do arquivo js java_js_exemplo.js para o apresentado na Listagem 7.
// Recupera objeto e exibe formatação de data padrão brasileiro print(new java.text.SimpleDateFormat(“dd/MM/yyyy”).format(new java.util.Date())); // Recupera objeto e exibe formatação de número decimal print(new java.text.DecimalFormat(“###.##”).format(345.345));
Ao executar o código em questão, você terá um resultado como, de acordo com o dia que estiver a rodar o mesmo:
- 17/06/2014
- 345,35
Dessa forma, é possível executar ações mais complexas e que exijam explicitamente mais processamento direto da própria JVM.
Como mencionado anteriormente, um dos recursos mais poderosos do Nashorn é a possibilidade de chamar classes Java de dentro do JavaScript. Você pode não apenas acessar classes e criar instâncias, mas também pode criar subclasses delas, chamar seus membros estáticos, e fazer praticamente qualquer coisa que você poderia fazer a partir de Java.
Para exemplificar, vejamos o velho recurso das Threads no Java. O JavaScript não tem os recursos de linguagem para concorrência e todos os runtimes comuns são single-thread ou pelo menos sem qualquer estado compartilhado. É interessante ver que, no ambiente Nashorn, o JavaScript poderia, de fato executar simultaneamente e com o estado compartilhado, assim como no Java. Para conferir tal comportamento, crie um novo arquivo JavaScript e nomeie-o de “java_threads.js”, e adicione em seguida o código contigo na Listagem 8 ao mesmo.
function loop() { for (i = 0; i < 5; i++) { print(“Quantidade de tentativas: “ + i); java.lang.Thread.sleep(2000); } iniciarThread(); } loop(); function iniciarThread() { // Esse código acessa a classe Java Thread diretamente var Thread = Java.type(“java.lang.Thread”); // Subclasse que iremos usar para chamar o método run var MyThread = Java.extend(Thread, { run : function() { print(“Executou em uma Thread separada!”); } }); var th = new MyThread(); th.start(); th.join(); }
Observe a presença de duas funções distintas no exemplo anterior. Ambas retratam usos diferentes do recurso de Threads no Java: a primeira explicita o uso do método sleep() para pausar uma execução na thread corrente pelo tempo passado por parâmetro em milissegundos, enquanto a segunda mostra um exemplo fiel de como criar um thread em Java, com a implementação sobrescrita do seu método run() bem como a respectiva chamada ao método start() da mesma, garantindo, assim, a execução em paralelo dos dois códigos. Repare também que a forma canônica para acessar uma classe do Nashorn é usando Java.type e você pode estender uma classe usando Java.extend.
O resultado da execução desse código será a impressão, pausada em dois segundos cada, da mensagem informando a quantidade de tentativas atualizada pela variável contadora, assim como a mensagem do segundo método de execução paralela.
Isso mostra que o poder de comunicação entre as linguagens é perpassado e vai de encontro justamente ao que ambas tem de mais comum nesse novo ambiente de execução: a geração final de bytecode Java.
Passando dados para e a partir do Java
Como indicado nos exemplos anteriores, você pode chamar código JavaScript diretamente do seu código Java; para tal, basta obter a engine e chamar o seu método “eval”. Além disso, você também pode passar dados explicitamente como strings ou passar bindings de Java que podem ser acessados como variáveis globais de dentro da engine JavaScript.
Veja na Listagem 9 exemplos comentados de como isso poderia ser feito facilmente. Para executar os exemplos basta realizar o mesmo procedimento dos anteriores.
// Método de execução básica a partir do Java private void testeValoresAPartirDoJava() { try { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine engine = scriptEngineManager.getEngineByName(“nashorn”); String nome = “Julio”; // Os valores em Java podem ser passados em associação comum de strings engine.eval(“print('Oi, “ + nome + “! Tudo bem?')”); } catch (ScriptException e) { e.printStackTrace(); } } // Método de execução básica a partir do JavaScript idade = 30; // O valor é recuperado e passado para um objeto SimpleBindings SimpleBindings bindingSimples = new SimpleBindings(); simpleBindings.put(“globalValue”, idade); // No momento de traduzir a expressão, o binding é feito nashorn.eval(“print (globalValue)”, bindingSimples);
Listas e Collections
Certamente, um dos recursos mais favoritos e usados da linguagem Java em contraste às suas linguagens “fundadoras” ou anteriores, é o uso da API de Collections e suas hierarquias. Nada mais justo que entender também como funciona a comunicação das duas linguagens em relação a essa famosa estrutura de dados, no que concerne aos conceitos de programação.
Por todos os aspectos, com o lançamento do JDK 8, Java tem, pelo menos até certo ponto, se tornado uma linguagem funcional. Agora você pode usar funções de ordem superior em coleções, por exemplo, para iterar sobre seus elementos. A função de ordem superior é uma função que recebe outra função como um parâmetro e faz algo significativo com ele. Vamos analisar o código contigo na Listagem 10 e discorrer sobre o assunto.
@SuppressWarnings({ “unchecked”, “rawtypes” }) private void testeFuncaoOrdemSuperior() { List<? extends Object> listaGenerica = Arrays.asList(“ABC”, 4, new Object(), 2.45); listaGenerica.forEach(new Consumer() { @Override public void accept(Object object) { System.out.println(object); } }); }
Neste exemplo, em vez de iterar sobre os elementos usando um loop “externo” como seria tradicionalmente tem feito, agora passamos uma função “Consumer” para a operação de “forEach”, uma operação de ordem superior “internal-loop”, que executa o método “accept” do Consumer passando em cada elemento da lista, um por um.
Como já mencionado, a abordagem da linguagem funcional para tal função de ordem superior aceitaria sim um parâmetro de função, em vez de um objeto. Passar referências às funções por si só não é algo tradicionalmente Java, mas o JDK 8 agora tem recursos sintáticos mais elegantes para sintetizar o uso de expressões lambda. Por exemplo:
List<? extends Object> listaGenerica = Arrays.asList(“ABC”, 4, new Object(), 2.45); listaGenerica.forEach(elemento -> System.out.println(elemento));
Neste caso, o parâmetro de “forEach” tem a forma de uma função de referência. Isto é possível porque o Consumer agora é uma interface funcional, (às vezes chamado de um tipo Single Abstract Model, ou “SAM”).
Então por que estamos falando de lambdas em um artigo sobre Nashorn? Porque em JavaScript você também pode escrever código como este e Nashorn é especialmente bem preparado para fazer a ponte entre Java e JavaScript, neste caso. Em particular, ele permite que você mesmo passe funções JavaScript simples como implementações das interfaces funcionais (tipos SAM).
Vamos dar uma olhada no mesmo código escrito na Listagem 10, porém agora em JavaScript, que faz a mesma coisa que o nosso código Java anterior. Note que não há nenhum tipo de lista embutida no JavaScript, apenas arrays; mas esses arrays são dimensionados de forma dinâmica e tem métodos comparáveis aos de uma lista Java. Assim, neste exemplo, estamos chamando o método “forEach” de um array JavaScript (Listagem 11).
function testeFuncaoOrdemSuperior() { var listaGenerica = [ 'ABC', 4, new java.lang.Object(), 2.45 ]; listaGenerica.forEach(function(elemento) { print(elemento); }); // Ou.. var lista = java.util.Arrays.asList(listaGenerica); lista.forEach(function(elemento) { print(elemento) } ); } testeFuncaoOrdemSuperior();
O Nashorn permite-nos fornecer referências das funções JavaScript simples onde se espera uma interface funcional (tipo SAM). Este é, portanto, não somente possível a partir do Java, mas também a partir do JavaScript.
A próxima versão do ECMAScript, que deverá se tornar definitiva este ano, irá incluir uma breve sintaxe para as funções que lhes permitam ser escrita quase como lambdas Java, exceto que ele usará uma seta dupla =>.
Avatar.js
Vimos que com o Nashorn temos uma engine JavaScript Premium embutida no Java. Vimos também que a partir do Nashorn podemos acessar qualquer classe Java. O Avatar.js vai um passo além e traz “o modelo de programação Node, APIs e módulo de ecossistema para a plataforma Java.” Para entender o que isso significa e por que é emocionante, primeiro temos que entender o que é o Node. O Node basicamente extrai a engine V8 JavaScript do Chrome para que ela funcione a partir da linha de comando sem a necessidade de um navegador. Isso faz do JavaScript executável não só no navegador, mas também no lado do servidor. Para executar JavaScript em um servidor de forma significativa pelo menos você vai precisar acessar o sistema de arquivos e a rede. Para conseguir isso, o Node embute uma biblioteca chamada libuv que faz isso de forma assíncrona. Praticamente, isto significa que as chamadas para o sistema operacional nunca são bloqueadas, mesmo se elas demorarem um pouco para voltar. Em vez de bloquear, você fornece uma função de callback que será acionada assim que a chamada é feita, entregando os resultados, se houver algum.
NOTA:
O foco deste artigo não será em nenhum momento explanar acerca do NodeJS ou quaisquer tecnologias semelhantes. Pressupõe-se que o leitor tenha tais conhecimentos ou busque os mesmos. Nessa edição temos dois artigos que tratam desse assunto.
Vejamos, portanto, um exemplo extraído diretamente do site do NodeJS (Listagem 12).
// carrega o módulo “http” para lidar com as requisições http var http = require('http'); // Quando há uma requisição retorna a mensagem 'Hello, World\n' function handleRequest(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello, World\n'); } /* Estamos ouvindo no localhost, na porta 1337 e chamamos o handleRequest como call back você verá uma natureza non-blocking/asynchronous nessa implementação */ http.createServer(handleRequest).listen(1337, '127.0.0.1'); // Escreve no console para garantir que estamos no caminho certo console.log('Get your hello at http://127.0.0.1:1337/');
Conforme mencionamos anteriormente, você necessitará instalar o NodeJS para verificar a execução desse exemplo. Aqui nos ateremos a demonstrar como funciona a comunicação entre ambas as tecnologias, ficando a cargo do autor se aprofundar na linguagem caso queira.
O objetivo do Avatar.js é fornecer o mesmo conjunto de APIs core que o Node apresenta, através da construção de classes Java usando a famosa “libuv” e fazendo-as, então, acessíveis via JavaScript. Apesar desse tipo de implementação parecer um pouco estranha e rústica, ela costuma funcionar muito bem. O Avatar.js suporta um grande número de módulos NodeJS além do suporte nativo ao “express”: uma stream principal do framework web para o Node, o que indica, sobretudo, que esse tipo de recurso pode trabalhar em conjunto com um grande número de projetos já existentes.
Infelizmente, no momento de escrita deste artigo não existe nenhuma distribuição binária para o Avatar.js para o contexto de implementação que sugerimos. Se acessar a página do projeto poderá ver um arquivo readme que explica como construir tudo a partir do código fonte.
Uma vez que você tenha instalado os binários e os colocado dentro da pasta lib, você poderá então chamar o Avatar.js usando algo como:
java -Djava.library.path=lib -jar lib/avatar-js.jar helloWorld.js
E mais uma vez podemos nos perguntar... Mas onde isso tudo pode ser útil? A Oracle vê uma série de casos de uso para essa biblioteca, bem como várias situações de usabilidade para a mesma dentro dos diferentes contextos de implementação. Dentre as opções oficiais, duas se destacam:
- Você tem uma aplicação NodeJS e quer usar algumas bibliotecas externas para complementar a API;
- Você quer mudar para o JavaScript e para a API do Node, mas você tem código legado em Java que não funcionaria totalmente fora de um ambiente com JVM.
Em ambas situações funcionam bem com o Avatar.js, além de poder fazer qualquer chamada a classes Java a partir do código JavaScript, que é suportado naturalmente pelo Nashorn, como vimos até então.
Vamos supor um cenário em que, como sabemos, o JavaScript tenha apenas um simples tipo para expressar números: o tipo “number”. Isso seria equivalente ao “double” do Java, no quesito precisão e limitações. Números em JavaScript, assim como os doubles em Java, não são aptos para expressar intervalos arbitrários e precisão, quando por exemplo lidamos com cálculos sobre valores monetários.
Em Java, você pode usar a classe BigDecimal, que contém algumas dúzias de métodos que te ajudarão a lidar com toda essa situação facilmente, ou até mesmo usar bibliotecas terceiras para resolver o problema. Mas já o JavaScript não tem nada equivalente em seu core, então você pode simplesmente pegar emprestado o uso da classe BigDecimal a partir do seu código JavaScript e ter, dessa forma, o mesmo comportamento que teria no Java.
Vejamos o exemplo ilustrado na Listagem 13, onde temos um serviço web que calcula o percentual sobre uma determinada quantia monetária.
var BigDecimal = Java.type('java.math.BigDecimal'); function calcularPercentual(quantia, percentual) { var resultado = new BigDecimal(quantia).multiply( new BigDecimal(percentual)).divide( new BigDecimal(“100”), 2, BigDecimal.ROUND_HALF_EVEN); return resultado.toPlainString(); } public static String calcular(String quantia, String percentual) { BigDecimal resultado = new BigDecimal(quantia).multiply( new BigDecimal(percentual)).divide( new BigDecimal(“100”), 2, BigDecimal.ROUND_HALF_EVEN); return resultado.toPlainString(); } // carrega o módulo utility 'url' para url var url = require('url'); function handleRequest(req, res) { // '/calcular' é o path do nosso web service if (url.parse(req.url).pathname === '/calcular') { var query = url.parse(req.url, true).query; // quantia and percentual are passed in as query parameters var resultado = calcularPercentual(query.quantia, query.percentual); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(resultado + '\n'); } }
Em JavaScript não existem tipos declarados, mas se observar bem os dois primeiros métodos da listagem, perceberá a extrema semelhança entre as implementação em JavaScript e Java, respectivamente.
Para efetuar o teste integrado basta adicionar a mesma à Listagem 12 anterior, modificando as partes devidas para tal, tal como ilustrado no método handleRequest da Listagem 13. Dentro de um ambiente montado e funcional, o teste poderá ser efetuado acessando uma URL como: http://localhost:1337/calcular?quantia=1943534500534534000008654613&percentual=6.45
O resultado seria a impressão do valor calculado no próprio browser. Analisando o ponto de vista de usabilidade para essa tecnologia, teríamos algumas outras onde isso se aplicaria tal como quando você decide migrar sua aplicação JEE existente para JavaScript e Node. Nesse caso, você pode facilmente acessar todos os seus serviços existentes a partir do JavaScript. Outro caso de uso seria ter uma nova funcionalidade no servidor escrita em JavaScript e Node que ainda poderia ser beneficiada a partir dos serviços JEE existentes.
Indo na mesma direção podemos encontrar ainda o Projeto Avatar que é baseado no Avatar.js. A ideia básica é escrever sua aplicação em JavaScript e acessar os serviços JEE. O Projeto Avatar vem com uma distribuição binária combinada para o Avatar.js, mas requer o Glassfish para instalação e desenvolvimento, além de servidor base.
Nashorn e JavaFX
O JavaFX é um conjunto de gráficos e pacotes de mídia que possibilita aos desenvolvedores criar, estilizar, testar, debugar e iniciar aplicações ricas no cliente e que operam consistentemente sobre diversas plataformas.
Com o Nashorn, você pode interpretar um script JavaFX usando o comando jjs com a opção –fx na linha de comando. Vejamos o exemplo desenvolvido na Listagem 14.
var Button = javafx.scene.control.Button; var StackPane = javafx.scene.layout.StackPane; var Scene = javafx.scene.Scene; function start(estagioInicial) { estagioInicial.title = “Ola Mundo!”; var button = new Button(); button.text = “Dizer 'Ola Mundo!'“; button.onAction = function() print(“Ola Mundo!”); var root = new StackPane(); root.children.add(button); estagioInicial.scene = new Scene(root, 300, 250); estagioInicial.show(); }
Para executar esse exemplo, crie um novo arquivo chamado “javafx_testes.js” dentro do diretório onde se encontra o .bat do jjs, isto é, na pasta bin do seu JDK 8, adicione o código da Listagem 14 ao mesmo e execute o comando a seguir, tal como ilustrado na Figura 5.
jjs -fx -scripting javafx_testes.js
O resultado da execução será semelhante ao exibido na Figura 6.
Note também que ao efetuar o clique no botão disponibilizado, a mensagem “Ola Mundo!” será exibida no Console do seu prompt.
Debugando Nashorn
Uma das desvantagens do Nashorn é que o mesmo não inclui nenhum tipo de ferramenta de auxílio a debug. Felizmente, tanto o NetBeans 8 quanto o IntelliJ IDEA a partir da sua versão 13.3 suportam esse tipo de funcionalidade via JavaScript. Os recursos são facilmente encontrados e de boa usabilidade, concentrando-se num item de menu que abre uma pop-up nos arquivos JavaScript permitindo que você debugue seu código.
No IntelliJ você pode configurar breakpoints nos arquivos Java e JavaScript usando atalhos também. Quando você configura breakpoints automaticamente assume todo o poder de debugging usual.
O Nashorn é uma tecnologia que foi desenhada para ser um substituto mais rápido e melhor para a antiga engine Rhino, e até então tem mostrado sucesso nessa caminhada. A tecnologia ainda não se encontra no seu nível ideal, muitas correções precisam ser feitas e bugs precisam ser analisados e reparados. Porém, para o que há até então, com certeza a tecnologia supera as expectativas e vai além, permitindo flexibilidade inclusive na migração de códigos dessa natureza, envolvendo as tão famosas e importantes linguagens Java e JavaScript.
O futuro ainda reserva muito para as tecnologias em questão, e certamente o Nashorn irá estar presente, melhorando cada vez mais.