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.
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.
Aprender
- 5 coisas que você não sabia sobre ... : Descubra o quanto você não sabe sobre a plataforma Java, nesta série dedicada a informações pouco importantes sobre Java em dicas úteis de programação.
- "Invoque dinamicamente linguagens dinâmicas, Parte 1: Introdução à API de script do Java" (Tom McQueeney, developerWorks, setembro de 2007): A Parte 1 deste artigo em duas partes introduz os recursos da API de script do Java; a Parte 2 se aprofunda em seus vários aplicativos poderosos.
- "JavaScript EE, Parte 3: Use a API de script do API com JSP" (Andrei Cioroianu, developerWorks, junho de 2009): Saiba mais sobre a combinação do JavaScript com a plataforma Java e como construir interfaces de usuário Ajax que permaneçam funcionais quando o JavaScript é desativado no navegador da web.
- Ferramentas e utilitários do JDK: Saiba mais sobre as ferramentas experimentais de monitoração e solução de problemas discutidas no foco de 5 coisas em monitoração de desempenho, incluindo
jmap. - A área de tecnologia Java do developerWorks: Centenas de artigos sobre cada aspecto da programação Java.
Discutir
- Envolva-se na comunidade My developerWorks. Entre em contato com outros usuários do developerWorks enquanto explora os blogs, fóruns, grupos e wikis dos desenvolvedores.
