Introdução ao Nashorn
1. Introdução
Este artigo enfocaNashorn –, o novo mecanismo JavaScript padrão para JVM a partir do Java 8.
Muitas técnicas sofisticadas foram usadas para fazer com queNashorn ordens de magnitude tenham mais desempenho do que seu predecessor, chamadoRhino,, portanto, é uma mudança que vale a pena.
Vamos dar uma olhada em algumas das maneiras como ele pode ser usado.
2. Linha de comando
O JDK 1.8 inclui um interpretador de linha de comando chamadojjs que pode ser usado para executar arquivos JavaScript ou, se iniciado sem argumentos, como um REPL (shell interativo):
$ $JAVA_HOME/bin/jjs hello.js
Hello World
Aqui, o arquivohello.js contém uma única instrução:print(“Hello World”);
O mesmo código pode ser executado da maneira interativa:
$ $JAVA_HOME/bin/jjs
jjs> print("Hello World")
Hello World
Você também pode instruir o tempo de execução * nix a usarjjs para executar um script de destino, adicionando#!$JAVA_HOME/bin/jjs como a primeira linha:
#!$JAVA_HOME/bin/jjs
var greeting = "Hello World";
print(greeting);
E então o arquivo pode ser executado normalmente:
$ ./hello.js
Hello World
3. Embedded Script Engine
A segunda e provavelmente mais comum maneira de executar JavaScript de dentro da JVM é por meio doScriptEngine. JSR-223 define um conjunto de APIs de script, permitindo uma arquitetura de mecanismo de script plugável que pode ser usada para qualquer linguagem dinâmica (desde que tem uma implementação JVM, é claro).
Vamos criar um mecanismo JavaScript:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
Object result = engine.eval(
"var greeting='hello world';" +
"print(greeting);" +
"greeting");
Aqui, criamos um novoScriptEngineManager e imediatamente pedimos que ele nos forneça umScriptEngine chamadonashorn. Então, passamos algumas instruções e obtemos o resultado que, previsivelmente, acaba sendo aString “hello world“.
4. Passando dados para o script
Os dados podem ser passados para o mecanismo definindo um objetoBindings e passando-o como um segundo parâmetro para a funçãoeval:
Bindings bindings = engine.createBindings();
bindings.put("count", 3);
bindings.put("name", "example");
String script = "var greeting='Hello ';" +
"for(var i=count;i>0;i--) { " +
"greeting+=name + ' '" +
"}" +
"greeting";
Object bindingsResult = engine.eval(script, bindings);
Executar este trecho produz: “Hello example example example“.
5. Invocar funções JavaScript
Obviamente, é possível chamar funções JavaScript a partir do seu código Java:
engine.eval("function composeGreeting(name) {" +
"return 'Hello ' + name" +
"}");
Invocable invocable = (Invocable) engine;
Object funcResult = invocable.invokeFunction("composeGreeting", "example");
Isso retornará “Hello example“.
6. Usando objetos Java
Como estamos executando na JVM, é possível usar objetos Java nativos no código JavaScript.
Isso é feito usando um objetoJava:
Object map = engine.eval("var HashMap = Java.type('java.util.HashMap');" +
"var map = new HashMap();" +
"map.put('hello', 'world');" +
"map");
7. Extensões de linguagem
Nashorn is targeting ECMAScript 5.1, mas fornece extensões para tornar o uso do JavaScript um pouco mais agradável.
7.1. Iterando coleções com For-Each
For-each é uma extensão conveniente para tornar a iteração em várias coleções mais fácil:
String script = "var list = [1, 2, 3, 4, 5];" +
"var result = '';" +
"for each (var i in list) {" +
"result+=i+'-';" +
"};" +
"print(result);";
engine.eval(script);
Aqui, unimos elementos de uma matriz usando a construção de iteraçãofor-each.
A saída resultante será1-2-3-4-5-.
7.2. Literais de função
Em declarações de função simples, você pode omitir chaves:
function increment(in) ++in
Obviamente, isso só pode ser feito para funções simples de uma linha.
7.3. Cláusulas de captura condicional
É possível adicionar cláusulas de captura protegida que são executadas apenas se a condição especificada for verdadeira:
try {
throw "BOOM";
} catch(e if typeof e === 'string') {
print("String thrown: " + e);
} catch(e) {
print("this shouldn't happen!");
}
Isso imprimirá “String thrown: BOOM“.
7.4. Matrizes digitadas e conversões de tipo
É possível usar matrizes do tipo Java e converter para e de matrizes JavaScript:
function arrays(arr) {
var javaIntArray = Java.to(arr, "int[]");
print(javaIntArray[0]);
print(javaIntArray[1]);
print(javaIntArray[2]);
}
Nashorn executa algumas conversões de tipo aqui para garantir que todos os valores da matriz JavaScript digitada dinamicamente possam caber nas matrizes Java somente de inteiros.
O resultado da chamada da função acima com o argumento[100, “1654”, true] resulta na saída de 100, 1654 e 1 (todos os números).
Os valoresStringe booleanos foram convertidos implicitamente em suas contrapartes inteiras lógicas.
7.5. Definindo o protótipo do objeto comObject.setPrototypeOf
Nashorn define uma extensão de API que nos permite alterar o protótipo de um objeto:
Object.setPrototypeOf(obj, newProto)
Esta função é geralmente considerada a melhor alternativa paraObject.prototype.proto, portanto, deve ser a maneira preferida de definir o protótipo do objeto em todos os novos códigos
7.6. noSuchProperty enoSuchMethod mágico
É possível definir métodos em um objeto que serão invocados sempre que uma propriedadeundefined for acessada ou um métodoundefined for invocado:
var demo = {
__noSuchProperty__: function (propName) {
print("Accessed non-existing property: " + propName);
},
__noSuchMethod__: function (methodName) {
print("Invoked non-existing method: " + methodName);
}
};
demo.doesNotExist;
demo.callNonExistingMethod()
Isso imprimirá:
Accessed non-existing property: doesNotExist
Invoked non-existing method: callNonExistingMethod
7.7. Vincular propriedades do objeto comObject.bindProperties
Object.bindProperties pode ser usado para vincular propriedades de um objeto a outro:
var first = {
name: "Whiskey",
age: 5
};
var second = {
volume: 100
};
Object.bindProperties(first, second);
print(first.volume);
second.volume = 1000;
print(first.volume);
Observe que isso cria uma ligação "ativa" e quaisquer atualizações no objeto de origem também são visíveis através do destino da ligação.
7.8. Localizações
O nome do arquivo atual, diretório e uma linha podem ser obtidos nas variáveis globaisFILE, DIR, LINE:
print(__FILE__, __LINE__, __DIR__)
7.9. Extensões para String.prototype
Existem duas extensões simples, mas muito úteis, queNashorn fornece no protótipoString. Estas são as funçõestrimRightetrimLeft que, sem surpresa, retornam uma cópia deString com o espaço em branco removido:
print(" hello world".trimLeft());
print("hello world ".trimRight());
Irá imprimir “olá mundo” duas vezes sem espaços à esquerda ou à direita.
7.10. FunçãoJava.asJSONCompatible
Usando esta função, podemos obter um objeto que seja compatível com as expectativas das bibliotecas Java JSON.
Ou seja, se ele próprio, ou qualquer objeto transitivamente alcançável por meio dele, for um array JavaScript, então tais objetos serão expostos comoJSObject que também implementa a interfaceList para expor os elementos do array.
Object obj = engine.eval("Java.asJSONCompatible(
{ number: 42, greet: 'hello', primes: [2,3,5,7,11,13] })");
Map map = (Map)obj;
System.out.println(map.get("greet"));
System.out.println(map.get("primes"));
System.out.println(List.class.isAssignableFrom(map.get("primes").getClass()));
Isso imprimirá “hello” seguido por[2, 3, 5, 7, 11, 13] seguido portrue.
8. Carregando scripts
Também é possível carregar outro arquivo JavaScript deScriptEngine:
load('classpath:script.js')
Um script também pode ser carregado a partir de uma URL:
load('/script.js')
Lembre-se de que o JavaScript não tem um conceito de namespaces, portanto, tudo é empilhado no escopo global. Isso possibilita que scripts carregados criem conflitos de nomenclatura com seu código ou com o outro. Isso pode ser atenuado usando a funçãoloadWithNewGlobal:
var math = loadWithNewGlobal('classpath:math_module.js')
math.increment(5);
Com o seguintemath_module.js:
var math = {
increment: function(num) {
return ++num;
}
};
math;bai
Aqui estamos definindo um objeto chamadomath que tem uma única função chamadaincrement.. Usando esse paradigma, podemos até mesmo emular a modularidade básica!
8. Conclusão
Este artigo explorou alguns recursos do mecanismoNashorn JavaScript. Os exemplos apresentados aqui usaram scripts literais de string, mas para cenários da vida real, você provavelmente deseja manter seu script em arquivos separados e carregá-los usando uma classeReader.
Como sempre, o código neste artigo está disponívelover on Github.