Artigos Técnicos

Artigos Técnicos de seus amigos na Oracle e na comunidade de desenvolvedores.

Nashorn prático, Parte 2: O Java em JavaScript

O mecanismo Nashorn foi descontinuado no JDK 11 como parte da JEP 335 e foi removido de JDK15 como parte da JEP 372. Para saber mais, leia o Guia de Migração do Nashorn para o GraalVM JavaScript.

Esta série de artigos apresenta o Nashorn, um mecanismo de runtime JavaScript extremamente rápido que vem com o Java SE 8 para oferecer um ambiente leve para estender, complementar e, muitas vezes, até mesmo substituir o código Java. Recursos completos de Java Virtual Machine (JVM) e a digitação dinâmica representam um conjunto de ferramentas eficaz que será atraente para os desenvolvedores e administradores

Depois que a Sun Microsystems selou o acordo de licenciamento em dezembro de 1995, a linguagem que começou na Netscape como Mocha e mais tarde em LiveScript, tornou-se JavaScript - tendo literalmente nada em comum com Java. O único motivo para a rebranding na época era aumentar a popularidade do JavaScript dado o recente lançamento do Java 1.0. O lançamento do Nashorn traz um encerramento interessante para a história, dado o grau de integração destas duas línguas. A Sun e, posteriormente, a Oracle tentaram explorar as possibilidades de runtimes dinâmicos e foram bem-sucedidos em levar recursos de desenvolvimento rápido para a plataforma Java Virtual Machine (JVM).

A introdução da API de Script Java no Java 6 mudou a forma como a JVM é percebida no mundo do software. Enquanto Java permanece o cidadão principal, agora há um compartilhamento justo do desenvolvimento de JVM com código Scala, Ruby ou Python por meio da API de Script Java. Com a inclusão do Nashorn no Java SE 8, agora é possível aproveitar scripts sem dependências de terceiros, porque o Nashorn se tornou parte das ofertas oficiais de JDK e Java runtime environment (JRE).

Scripting para a Plataforma Java

A API de Script Java permite integração perfeita de qualquer linguagem compatível diretamente com o objeto ScriptEngine. As linguagens do JSR 223 (Scripting for the Java Platform) são descobertas pela JVM automaticamente, fornecendo uma implementação ScriptEngineFactory para a SPI (Service Provider Interface).

A listagem 1 demonstra o uso da API do Java Scripting para enumerar todos os runtimes compatíveis com JSR 223 disponíveis para a JVM. Observe que o arquivo jython-standalone-2.7.1.jar foi adicionado ao classpath, enquanto o Nashorn não requer nenhuma declaração e está sempre disponível. A função printEngine lista propriedades típicas de uma fábrica de scripts, fornecendo informações de saída consistentes sobre mecanismos no runtime.

/* engines.js */
    var printEngine = function(e) {
        print(java.lang.String.format("%s %s -- %s",
            e.getEngineName(), 
            e.getEngineVersion(), 
            e.getExtensions(), 
            e.getLanguageName()));
    }
    
    var engines = new javax.script.ScriptEngineManager().getEngineFactories();
    engines.stream().forEach(printEngine); 
    
    $ jjs -J-Djava.class.path=lib/jython-standalone-2.7.1.jar engines.js
    Oracle Nashorn 1.8.0_144 -- [js]
    jython 2.7.1 -- [py]

Lista 1. Nashorn em execução com Jython na JVM.

No exemplo da Listagem 1, junto com o código ScriptEngine, o Nashorn está usando diretamente a interface de Fluxo do Java 8 e o fechamento da expressão fornecido para forEach - se a função tiver um tipo de retorno simples, as chaves e a palavra-chave return poderão ser omitidas. Uma expressão como function(v) v é um equivalente conveniente da declaração function(v) { return v; }, que é muito útil para trabalhar com APIs Java 8.

O JDK vem com um lançador dedicado para programas JSR 223 chamados jrunscript. Se ele for chamado sem parâmetros, vai direto para o mecanismo Nashorn. Essa é outra maneira de invocar Nashorn, que é especialmente útil para uma linha. Na Listagem 2, o Nashorn se comunica com o MXBean do sistema operacional Java Management Extensions (JMX) para obter informações sobre a quantidade total de memória física disponível para a JVM.

$ jrunscript.exe -e "print(java.lang.management.ManagementFactory.getOperatingSystemMXBean().getTotalPhysicalMemorySize())"
    12566515712

Lista 2. Usando a ferramenta jrunscript para chamar o código Nashorn em linha no sistema operacional MXBean.

Nashorn do Java

O elemento-chave da API de Script Java é o objeto ScriptEngine, que vincula mecanismos à JVM e permite total interatividade. Os objetos podem ser enviados de e para o mecanismo com métodos get e put simples. As funções são acessadas com a família de métodos invoke e scripts inteiros podem ser enviados ao mecanismo com a combinação da função eval e do objeto FileReader.

O recurso de gerenciador de mecanismo de script é muito simples de instanciar, conforme ilustrado na Listagem 3. O Java pode alimentar ScriptEngine com código avaliado ou isso pode ser feito por meio do método put. O que vem do mecanismo é o objeto ScriptObjectMirror, que requer algum processamento para ser convertido em tipos Java, a menos que tipos primitivos, como int ou String, sejam retornados.

No exemplo da Listagem 3, a variável JavaScript intArray precisa ser convertida em uma matriz de valores inteiros para inicializar a variável intArray. Da mesma forma, o array de string Java stringList é colocado e, em seguida, convertido de volta para o array de string e convertido em uma lista de strings. A principal diferença em como os objetos ScriptEngine são recuperados é que os objetos Nashorn devem ser recuperados como um wrapper ScriptObjectMirror, enquanto os objetos Java nativos que foram colocados no mecanismo podem ser recuperados sem objetos provisórios. Se o objeto de retorno aparecer como [Object object] por meio do método toString(), isso significa que ele precisa que a conversão e a conversão estejam acessíveis com o Java.

/* NashornDemo.java */
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    import java.util.Arrays;
    import java.util.List;
    import jdk.nashorn.api.scripting.ScriptObjectMirror;
    
    public class NashornDemo {
        public static void main(String[] args) throws ScriptException {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("nashorn");
    
            engine.eval("var intArray = [1, 2, 3];");
            ScriptObjectMirror mirror = (ScriptObjectMirror) engine.get("intArray");
            Integer[] intArray = mirror.to(Integer[].class);
            System.out.println(intArray.length);
    
            engine.put("stringList", new String[]{"jeden", "dwa", "trzy"});
            List<String> stringList = Arrays.asList((String[]) engine.get("stringList"));
            System.out.println(stringList);
        }
    }
    
    $ javac NashornDemo.java && java NashornDemo
    3
    [jeden, dwa, trzy]
    4534

Lista 3. Trocar objetos simples pelo Nashorn ScriptEngine.

Um caso de uso comum para implantar o ScriptEngine em aplicativos Java é oferecer extensibilidade sem a necessidade de usar a Interface do Provedor de Serviços (SPI), a Iniciativa do Gateway de Código Aberto (OSGi) ou outras estruturas de plug-in. A capacidade de permitir um código personalizado dentro de um aplicativo pode aprimorar bastante a adoção e trabalhar em torno de curvas íngremes de aprendizado de programação para usuários menos avançados.

O Nashorn também pode permitir que módulos JavaScript de terceiros sejam usados diretamente no código Java. Considere o programa de amostra na Listagem 4, que executa uma função na biblioteca popular Moment.js por meio da interface do mecanismo de script. O método eval é usado no objeto FileReader para carregar a biblioteca JavaScript e retornar um valor de string. Não há necessidade de colocar ScriptObjectMirror no meio porque as primitivas, como String ou int, são reconhecidas como primitivas Java.

import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import java.io.FileReader;
    
    public class NashornMoment {
        public static void main(String[] args) throws Exception {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByExtension("js");
            engine.eval(new FileReader("moment.js"));
            System.out.println(engine.eval("moment().format('LLL')"));
        }
    }

Lista 4. Chamar um método em um módulo JavaScript externo.

Todos os mecanismos criados com ScriptEngineManager com a família de funções getEngine não aceitam argumentos extras. Essa lacuna pode ser trabalhada em Nashorn fornecendo a JVM com a propriedade do sistema -Dnashorn.args. No código Java, ScriptEngineManager pode ser substituído por NashornScriptEngineFactory para que possa fornecer propriedades personalizadas como a compatibilidade com ECMAScript 6 (ES6) ou um carregador de classe personalizado. Se a compatibilidade com ES6 fosse omitida na Listagem 5, o script geraria ScriptException informando <eval>:1:0 Expected an operand but found const. Para saber todas as formas como os parâmetros podem ser fornecidos para o método getScriptEngine, consulte a referência da API JDK para a classe NashornScriptEngineFactory. Uma maneira equivalente de chamar esse mecanismo com apenas ScriptEngine seria fornecendo -Dnashorn.args=--language=es6 à lista de argumentos da JVM.

import javax.script.ScriptEngine;
    import javax.script.ScriptException;
    import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
    
    public class NashornFactory {
        public static void main(String[] args) throws ScriptException {
            NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
            ScriptEngine engine = factory.getScriptEngine("--language=es6");
            engine.eval("const a = 100");
        }
    }

Lista 5. Fábrica do mecanismo de script Nashorn usada para criar um mecanismo com atributos extras.

Nem tudo precisa ser retornado ao Java para processamento. A especificação do JavaScript contém um Objeto JSON incorporado que é usado para codificar e decodificar o formato JSON. O Nashorn pode efetivamente contornar a falta dessa funcionalidade no JDK 8 e em versões anteriores. O exemplo na Listagem 6 ilustra como o Nashorn pode aproveitar o conteúdo da Web obtido com o Java para extrair as informações meteorológicas necessárias com o JSON. (A API OpenWeatherMap requer a chave appid que foi higienizada do código-fonte.)

import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.URL;
    import java.util.stream.Collectors;
    
    public class NashornWeather {
    
        public static void main(String[] args) throws Exception {
            String response;
            String url = "http://api.openweathermap.org/data/2.5/weather?q=Krakow,PL";
            try (
                InputStreamReader is = new InputStreamReader(new URL(url).openStream());
                BufferedReader reader = new BufferedReader(is)) {
                response = reader.lines().collect(Collectors.joining());
            }
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("nashorn");
            engine.put("response", response);
            System.out.println(engine.eval("JSON.parse(response).weather[0].description"));
        }
    }

Lista 6. Usando o Nashorn para consumir uma API REST JSON.

Os escopos de execução do mecanismo de script podem ser isolados entre si com contextos e vinculações. Como o novo código carregado no interpretador Nashorn poderia desorganizar o escopo global, os scripts destinados à execução isoladamente podem ser iniciados por meio da função loadWithNewGlobal. O escopo global ENGINE_SCOPE é equivalente ao global do ECMAScript, que é acessado por meio do uso de nível superior da palavra-chave this. Para uso específico de ScriptContext, verifique as notas do mecanismo Nashorn jsr223 no wiki OpenJDK.

Java do Nashorn

O Nashorn pode trabalhar perfeitamente com objetos Java e, na maioria das vezes, não requer wrappers extras. Por padrão, pacotes principais, como com, edu, java, javafx, javax e org, são disponibilizados para o escopo global; portanto, não há necessidade de "importá-los". Outros pacotes podem ser referenciados com o objeto global Packages ou uma forma recomendada é com o método global Java.type. Nessas linhas, java.util.HashMap, Packages.java.util.HashMap e Java.type("java.util.HashMap") são três formas equivalentes de acessar o mapa hash Java do Nashorn.

Como o Nashorn é dinâmico, enquanto o Java é digitado estaticamente, a assinatura do método é determinada no runtime. Um exemplo do método sobrecarregado comumente usado println é mostrado na Listagem 7. Outra adição interessante foi a introdução do JEP 196: Nashorn Optimistic Typing, que ainda requer o parâmetro explícito -ot no JDK 8, mas se torna o comportamento padrão no JDK 9. Essa otimização do trabalho de adivinhação envolvido com suposições sobre tipos de Java referenciados pode diminuir ligeiramente o tempo de inicialização da JVM, mas melhora muito o desempenho durante o tempo de execução, com um código Nashorn aquecido próximo à metade da velocidade do mecanismo JavaScript V8.

jjs> java.lang.System.out.println
    [jdk.internal.dynalink.beans.OverloadedDynamicMethod
     void java.io.PrintStream.println()
     void java.io.PrintStream.println(boolean)
     void java.io.PrintStream.println(char)
     void java.io.PrintStream.println(char[])
     void java.io.PrintStream.println(double)
     void java.io.PrintStream.println(float)
     void java.io.PrintStream.println(int)
     void java.io.PrintStream.println(long)
     void java.io.PrintStream.println(Object)
     void java.io.PrintStream.println(String)
    ]

Lista 7. Método Java sobrecarregado de um fluxo de saída padrão visto pela Nashorn.

Embora a Nashorn não envie com mecanismos de introspecção incorporados por padrão, essas ferramentas podem ser facilmente implementadas. Consulte Listagem 8 para obter uma porta Nashorn da função dir() do Python, um método conhecido para examinar os atributos e métodos dos objetos. O comportamento exato de Python não foi totalmente reajustado, porque a natureza dinâmica dos carregadores de classe proíbe a enumeração de pacotes sem desembalar todas as entradas do classpath. No exemplo, a sintaxe concisa do Nashorn se torna ainda mais limpa com o uso de streams e lambdas do Java 8.

/* dir.js */
    var dir = function(obj, search) {
        with(new JavaImporter(java.util, java.util.stream)) {
            var members = new HashMap();
            var signature = function(method) {
                var s = method.getReturnType().getSimpleName() 
                    + " " + method.getName() + "(";
                s = s + Arrays.stream(method.getParameters()).map(function(p) {
                    return p.getType().getSimpleName() + " " + p.getName();
                }).collect(Collectors.joining(", ")) + ")";
                return s;
            }
            for (var c = obj.getClass(); c != null; c = c.getSuperclass()) {
                Arrays.stream(c.getFields()).forEach(function(f) {
                    members.putIfAbsent(f.getName(), f.getName());
                });
                Arrays.stream(c.getMethods()).forEach(function(m) {
                    members.putIfAbsent(m.getName(), signature(m));
                });
            }
            return members.entrySet().stream()
                .sorted(function(e1, e2) e1.getKey().compareTo(e2.getKey()))
                .map(function(e) e.getValue())
                .filter(function(p) !search || p.contains(search))
                .collect(Collectors.toList());
        }
    }
    
    jjs> dir(new java.util.HashMap())
    
    [void clear(), Object clone(), Object compute(Object arg0, BiFunction arg1), Object 
    computeIfAbsent(Object arg0, Function arg1), Object computeIfPresent(Object arg0, 
    BiFunction arg1), boolean containsKey(Object arg0), boolean containsValue(Object arg0),
    Set entrySet(), boolean equals(Object arg0), void forEach(BiConsumer arg0), Object
    get(Object arg0), Class getClass(), Object getOrDefault(Object arg0, Object arg1), int
    hashCode(), boolean isEmpty(), Set keySet(), Object merge(Object arg0, Object arg1, 
    BiFunction arg2), void notify(), void notifyAll(), Object put(Object arg0, Object arg1),
    void putAll(Map arg0), Object putIfAbsent(Object arg0, Object arg1), Object 
    remove(Object arg0), boolean replace(Object arg0, Object arg1, Object arg2), void 
    replaceAll(BiFunction arg0), int size(), String toString(), Collection values(), void wait()]

Lista 8. Usando a função helper dir() Nashorn para listar atributos e métodos de objeto.

O exemplo mostrado na Listagem 8 também exibe a cláusula with do JavaScript e o objeto JavaImporter provenientes do Rinoceronte, e ambos são dois recursos novos que foram transferidos do Rinoceronte da Mozilla para o Nashorn para compatibilidade com versões anteriores. Em cada situação em que a mistura com escopo global é indesejada, a cláusula with pode proteger o escopo interno, enquanto JavaImporter permite expor rapidamente o conteúdo do pacote. O JavaImporter nem sempre precisa confiar na cláusula with; um importador independente também pode ser conveniente.

O Nashorn é fornecido com várias extensões de linguagem que visam tornar a interoperabilidade da plataforma ainda mais perfeita. Um deles é a função Java.from para converter arrays Java em arrays JavaScript nativos. A função reversa Java.to converte um objeto JavaScript existente em um tipo Java solicitado, de acordo com a especificação ECMAScript. Um exemplo de seu uso é mostrado na Listagem 9.

jjs> var home = new java.io.File(".");
    
    jjs> home.listFiles()
    [Ljava.io.File;@2d1ef81a
    
    jjs> Java.from(home.listFiles())
    .\demo.js,.\dir.js,.\es6.js,.\jvm-npm.js,.\jython-standalone-2.7.1.jar, 
    .\listEngines.js,.\module.js,.\mustache.js,.\qunit-2.4.0.js,.\underscore.js,.\unittest.js

Lista 9. Demonstração da conversão de matrizes Nashorn.

O Nashorn é totalmente capaz de implementar classes abstratas e interfaces do Java usando o operador new e classes não abstratas por meio do método Java.extend. Consequentemente, as superclasses Java podem ser referências do código Nashorn por meio do método Java.super. Não é possível adicionar novos métodos por meio da sobrecarga; o Nashorn só pode implementar, estender e interagir com métodos existentes neste cenário. Para adicionar novos atributos de classe no runtime, um método Object.bindProperties especial pode ser usado. Um exemplo de extensão da interface Callable é mostrado na Listagem 10, em que a estrutura do Executor Java funciona perfeitamente com o Nashorn. Devido à digitação dinâmica usada em relação ao método submit, a implementação requer a assinatura exata do método executor["submit(java.util.concurrent.Callable)"].

var Callable = Java.type("java.util.concurrent.Callable");
    var Worker = Java.extend(Callable, {
        call: function() {
            return Math.random();
        }
    });
    
    var executor = java.util.concurrent.Executors.newFixedThreadPool(4);
    var random, futures = [];
    try {
        for (var i = 1; i <= 10; i++) {
            futures.push(executor["submit(java.util.concurrent.Callable)"](new Worker()));
        }
    
        var random = futures.map(function(f) f.get());
    } finally {
        executor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);
        executor.shutdownNow();
    }
    
    print(random);

Lista 10. Nashorn implementando a interface Executável.

Extensões

Além do suporte completo do ECMAScript 5.1 e do suporte selecionado para o ES6, o Nashorn apresenta várias extensões de idioma que podem enriquecer ainda mais o fluxo de desenvolvimento, muitas delas decorrentes do Mozilla Rhino. Os aprimoramentos notáveis incluem várias instruções try-catch, loops for each, funções anônimas e matrizes digitadas - criadas com Java.type("int[]") e vários métodos no objeto Object incorporado. Uma lista completa das extensões pode ser encontrada na página OpenJDK extensões Nashorn, um recurso essencial para começar a usar o idioma.

Figura 1. A página de extensões do Nashorn

Resumo

O Nashorn preenche o vazio entre Java e JavaScript que foi criado quando a Mozilla desenvolveu e renomeou o idioma. O nível de interoperabilidade entre o Nashorn e o Java demonstra um compromisso de longo prazo em manter e desenvolver ainda mais a linguagem, com novos recursos sendo enviados em versões menores do JDK. Como parte do JDK e do OpenJDK da Oracle, o Nashorn está pronto para uso na maioria dos sistemas ou para ser implantado facilmente com gerenciadores de pacotes nativos.

Sobre o autor

Przemyslaw Piotrowsk é engenheiro de software principal com mais de 10 anos de experiência no design, desenvolvimento e manutenção de sistemas de banco de dados. Ele é um Oracle Database 11 g Certified Master, um Oracle Database 12 c Certified Master e um Oracle Database Cloud Certified Master, que se concentra em engenharia de banco de dados e automação de infraestrutura.

Latest content

Explore and discover our latest tutorials

Funções sem servidor

As funções sem servidor fazem parte de uma evolução na computação em nuvem que ajudou a libertar as organizações de muitas das restrições de gerenciamento de infraestrutura e recursos.

O que é um blockchain?

Em termos gerais, um blockchain é um registro de transações imutável, mantido em uma rede distribuída de nós ponto a ponto (p2p). Basicamente, os blockchains servem como uma maneira descentralizada de armazenar informações.

OCI CLI

A CLI é uma ferramenta que ocupa pouco espaço, que você pode usar sozinha ou com a Console para executar tarefas do Oracle Cloud Infrastructure. A CLI fornece a mesma funcionalidade básica da Console, além de comandos adicionais.