Conteúdo


5 coisas que você não sabia sobre ... a API Java Scripting

Uma maneira mais fácil de criar scripts na plataforma Java

Comments

Atualmente, muitos desenvolvedores Java estão interessados em usar linguagem de script na plataforma Java, mas usar uma linguagem dinâmica que foi compilada no bytecode do Java nem sempre é possível. Em alguns casos, é mais rápido e eficiente simplesmente fazer scripts de partes de um aplicativo Java ou chamar os objetos Java particulares necessários de dentro de um script.

É onde entra o javax.script. A API de script do Java, introduzida no Java 6, acaba com a brecha entre pequenas linguagens úteis de script e o ecossistema robusto do Java. Com a API de script do Java, é possível rapidamente integral virtualmente qualquer linguagem de script com seu código Java, o que abre suas opções de forma considerável ao resolver pequenos problemas em domínios de outra pessoa.

1. Executando o JavaScript com jrunscript

Cada novo release da plataforma Java traz consigo um novo conjunto de ferramentas de linha de comando dentro do diretório bin do JDK. O Java 6 não foi exceção e o jrunscript não é uma adição pequena aos utilitários da plataforma Java.

Considere o problema básico de escrever um script de linha de comando para monitoração de desempenho. A ferramenta pegará emprestado o jmap (apresentado no artigo anterior da série) e o executará a cada 5 segundos com relação a um processo Java, de forma a obter uma ideia de como o processo está sendo executado. Normalmente, scripts de shell de linha de comandos servem, mas, neste caso, o aplicativo do servidor foi implantado em uma variedade de plataformas diferentes, incluindo Windows® e Linux®. Os administradores de sistema testemunharão que escrever scripts de shell que funcionem em ambas as plataformas é problemático. A solução normal é escrever um arquivo de lote do Windows e um script de shell do UNIX® e simplesmente manter os dois em sincronização no decorrer do tempo.

Mas, como qualquer leitor do The Pragmatic Programmer sabe, esta é uma grave violação do princípio DRY (don't repeat yourself - não se repita) e é um local propício para erros e defeitos. O que gostaríamos de fazer é escrever algum tipo de script independente do sistema operacional que possa ser executado em todas as plataformas.

A linguagem Java independe de plataforma, é claro, mas não é realmente o caso para uma linguagem de "sistema". O que precisamos é de uma linguagem de script — como o JavaScript, por exemplo.

A Listagem 1 inicia com um shell básico do que queremos:

Listagem 1. periodic.js
while (true) {     echo("Hello, world!"); }

Muitos, se não todos, desenvolvedores Java já conhecem JavaScript (ou ECMAScript; JavaScript é um dialeto do ECMAScript de propriedade da Netscape) graças às nossas interações forçadas com navegadores da web. A pergunta é, como um administrador de sistemas executaria este script?

A solução, é claro, é o utilitário jrunscript que vem com o JDK, como mostra a Listagem 2:

Listagem 2. jrunscript
C:\developerWorks\5things-scripting\code\jssrc>jrunscript periodic.js
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
...

Note que você também poderá usar um ciclo for para executar o script um certo número de vezes antes de sair. Basicamente, o jrunscript permite que você faça quase tudo o que normalmente faria com o JavaScript. A única exceção é que o ambiente não é um navegador, portanto não há DOM. As funções e objetos de nível superior disponíveis são, portanto, ligeiramente diferentes.

Como o Java 6 vem com o mecanismo Rhino ECMAScript como parte do JDK, o jrunscript pode executar qualquer código ECMAScript alimentado a ele, seja de um arquivo (como mostrado aqui) ou em um ambiente shell mais interativo chamado REPL ("Read-Evaluate-Print-Loop"). Simplesmente execute o jrunscript por si só para acessar o shell do REPL.

2. Acessando objetos Java a partir de um script

Ser capaz de escrever código em JavaScript/ECMAScript é bom, mas não queremos ter que reconstruir tudo o que usarmos na linguagem Java do zero — não é esta a finalidade. Felizmente, qualquer coisa que use os mecanismos da API de script do Java tem acesso completo ao ecossistema do Java pois, por dentro, tudo ainda é bytecode do Java. Portanto, voltando ao problema anterior, poderemos iniciar processos a partir da plataforma Java usando a chamada tradicional Runtime.exec(), como mostra a Listagem 3:

Listagem 3. Runtime.exec() executa jmap
var p = java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ])
p.waitFor()

A array arguments é a referência integrada padrão do ECMAScript aos argumentos passados a esta função. No caso do ambiente de script de nível superior, esta é a array de argumentos passada para o script em si (os parâmetros de linha de comando). Portanto, na Listagem 3, o script espera um argumento contendo o VMID do processo Java a mapear.

De forma alternativa, poderíamos tirar vantagem do fato de que jmap é uma classe Java e só chamar seu método main(), como na Listagem 4. Esta abordagem elimina a necessidade de "encadear" os fluxos in/out/err do objeto Process.

Listagem 4. JMap.main()
var args = [ "-histo", arguments[0] ] Packages.sun.tools.jmap.JMap.main(args)

A sintaxe Packages é uma notação do Rhino ECMAScript usada para referenciar um pacote Java fora dos pacotes principais java.* já configurados com o Rhino.

3. Chamando em scripts a partir do código Java

Chamando objetos Java a partir de um script é somente metade da história: O ambiente de script do Java também oferece a capacidade de invocar scripts de dentro do código Java. Fazer isto só requer instanciar uma ScriptEngine e, a seguir, carregar e avaliar o script, como mostra a Listagem 5:

Listagem 5. Script na plataforma Java
import java.io.*;
import javax.script.*;

public class App
{
    public static void main(String[] args)
    {
        try
        {
            ScriptEngine engine = 
                new ScriptEngineManager().getEngineByName("javascript");
            for (String arg : args)
            {
                FileReader fr = new FileReader(arg);
                engine.eval(fr);
            }
        }
        catch(IOException ioEx)
        {
            ioEx.printStackTrace();
        }
        catch(ScriptException scrEx)
        {
            scrEx.printStackTrace();
        }
    }
}

O método eval() também pode operar com relação a um String direto, de forma que o script não precisa vir de arquivos no sistema de arquivos — pode vir de um banco de dados, do usuário ou até mesmo ser construído dentro do aplicativo com base nas circunstâncias e na ação do usuário.

4. Vinculando objetos Java no espaço do script

Simplesmente invocar um script não é suficiente: Os scripts frequentemente querem interagir com objetos criados dentro do ambiente Java. Neste caso, o ambiente host do Java deverá criar objetos e vinculá-los, para que seja fácil para o script encontrá-los e usá-los. Esta é uma tarefa para o objeto ScriptContext mostrada na Listagem 6:

Listagem 6. Vinculando objetos para scripts
import java.io.*;
import javax.script.*;

public class App
{
    public static void main(String[] args)
    {
        try
        {
            ScriptEngine engine = 
                new ScriptEngineManager().getEngineByName("javascript");
                
            for (String arg : args)
            {
                Bindings bindings = new SimpleBindings();
                bindings.put("author", new Person("Ted", "Neward", 39));
                bindings.put("title", "5 Things You Didn't Know");
                
                FileReader fr = new FileReader(arg);
                engine.eval(fr, bindings);
            }
        }
        catch(IOException ioEx)
        {
            ioEx.printStackTrace();
        }
        catch(ScriptException scrEx)
        {
            scrEx.printStackTrace();
        }
    }
}

Acessar o objeto vinculado é direto — o nome do objeto vinculado é apresentado no nível do script como um membro do espaço de nome global, de forma que usar o objeto Person a partir do Rhino é tão fácil quanto o que mostra a Listagem 7:

Listagem 7. Quem escreveu este artigo?
println("Hello from inside scripting!")

println("author.firstName = " + author.firstName)

Como pode ser visto, propriedades no estilo JavaBeans são reduzidas a acessos diretos de nome, como se fossem campos.

5. Compilando scripts usados frequentemente

A desvantagem das linguagens de script sempre foi o desempenho. O motivo é que, na maioria dos casos, a linguagem de script é interpretada "no momento" e perde tempo e ciclos de CPU, tendo que analisar e validar texto à medida que é executada. Muitas linguagens de script em execução na JVM no final transformam o código de entrada em bytecode do Java, pelo menos na primeira vez que o script é analisado e validado; esta compilação dinâmica é descartada quando o programa Java é desligado. Manter scripts frequentemente usados em formato de bytecode daria uma significativa melhoria no desempenho.

Podemos fazer isto de uma forma natural e significativa com a API de script do Java. Se o ScriptEngine retornado implementar a interface Compilable, então o método de compilação naquela interface poderá ser usado para compilar o script (passado como String ou como Reader) em uma instância CompiledScript, que poderá, a seguir, ser usada para eval() o código compilado várias vezes, com diferentes vinculações. Isso é mostrado na Listagem 8.

Listagem 8. Compilando código interpretado
import java.io.*;
import javax.script.*;

public class App
{
    public static void main(String[] args)
    {
        try
        {
            ScriptEngine engine = 
                new ScriptEngineManager().getEngineByName("javascript");
                
            for (String arg : args)
            {
                Bindings bindings = new SimpleBindings();
                bindings.put("author", new Person("Ted", "Neward", 39));
                bindings.put("title", "5 Things You Didn't Know");
                
                FileReader fr = new FileReader(arg);
                if (engine instanceof Compilable)
                {
                    System.out.println("Compiling....");
                    Compilable compEngine = (Compilable)engine;
                    CompiledScript cs = compEngine.compile(fr);
                    cs.eval(bindings);
                }
                else
                    engine.eval(fr, bindings);
            }
        }
        catch(IOException ioEx)
        {
            ioEx.printStackTrace();
        }
        catch(ScriptException scrEx)
        {
            scrEx.printStackTrace();
        }
    }
}

Na maioria dos casos, a instância CompiledScript deverá ser mantida em algum lugar do armazenamento de longo prazo (servlet-context, por exemplo) de forma a evitar a recompilação do mesmo script repetidamente. No entanto, se o conteúdo do script mudar, será preciso criar um novo CompiledScript para refletir a mudança; uma vez compilado, o CompiledScript não mais executará o conteúdo do arquivo original do script.

Conclusão

A API de script do Java é um enorme passo na direção de estender o alcance e a funcionalidade de programas em Java e traz ganhos de produtividade associados com linguagens de script para dentro do ambiente Java. Acoplado com o jrunscript— que, obviamente, não é um programa completo de escrever —javax.script dá aos desenvolvedores Java os benefícios de linguagens de script, como Ruby (JRuby) e ECMAScript (Rhino), sem precisar render-se ao ecossistema e à escalabilidade do ambiente Java.

A seguir, na série 5 coisas: JDBC.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=507683
ArticleTitle=5 coisas que você não sabia sobre ... a API Java Scripting
publish-date=10072011