você está aqui: Home  → Arquivo de Mensagens

Scripting API - Integrando Java e JavaScript

Colaboração: Carlos Tosin

Data de Publicação: 11 de fevereiro de 2014

Introduzida a partir do Java 6, a Java Scripting API permite a integrar a linguagem Java com linguagens de script. Qualquer linguagem pode ser utilizada nesta integração, desde que ela se adeque ao que está especificado na JSR 223, que define os detalhes da Java Scripting API.

De forma nativa, a Java Scripting API tem suporte ao uso da linguagem JavaScript, o que permite a integração de aplicações Java com códigos escritos nesta linguagem. É justamente esta integração que será abordada a partir de agora.

O objetivo deste artigo é dar uma introdução ao uso da Java Scripting API na integração de aplicações Java com JavaScript. Para detalhes sobre aspectos mais avançados da API, consulte as referências no final do artigo.

Por que usar uma linguagem de script?

A grande vantagem de escrever partes do código de uma aplicação usando uma linguagem de script está no fato de podermos externalizar algo que pode ser alterado futuramente, facilitando a manutenção.

Imagine que você crie uma aplicação que faz alguns cálculos matemáticos que podem mudar com frequência. Se você definir os cálculos dentro da aplicação, toda vez que eles forem alterados você precisará mexer no código-fonte da aplicação, compilá-la novamente e redistribuí-la.

No entanto, se você definir os cálculos usando uma linguagem de script (como JavaScript), basta alterar o script, que pode ser definido em um arquivo texto qualquer. Da próxima vez que a aplicação for executada, ela irá ler o novo script e terá seu comportamento alterado.

Funcionamento Básico

O pacote javax.script agrupa as classes e interfaces da Java Scripting API. Para utilizar esta API, o primeiro passo é criar uma instância de ScriptEngineManager:

  ScriptEngineManager manager = new ScriptEngineManager();

O próximo passo é obter uma instância de ScriptEngine, que permite a integração entre o Java e uma linguagem de script específica. Nativamente, apenas a linguagem JavaScript é suportada, e o ScriptEngine associado a esta linguagem pode ser obtido da seguinte forma:

  ScriptEngine engine = manager.getEngineByName("JavaScript");

A partir de agora, chamar um código escrito em JavaScript já é possível. Suponha que o script esteja definido em um arquivo localizado em C:\Scripts\loop.js, dessa forma:

  for (var i = 0; i < 5; i++) {
   println("Número " + i);
  }

Este código, escrito em JavaScript, executa um loop 5 vezes, escrevendo uma mensagem. Se você não tem conhecimento de JavaScript, a lista de referências no final do artigo traz pode lhe ajudar. Para executarmos este script a partir da aplicação Java, usamos o método eval() do objeto ScriptEngine:

  engine.eval(new FileReader("C:\Scripts\loop.js"));

O método eval() executará o script definido dentro do arquivo. Este método também pode receber como parâmetro uma String que representa o script. Dessa forma não é preciso defini-lo em um arquivo. Por exemplo, este mesmo exemplo poderia ser executado da seguinte forma:

  engine.eval("for (var i = 0; i < 5; i++) { "
   + "println(\"Número \" + i); }");

Definindo Valores para Variáveis do Script

Em algumas situações, o script poderá necessitar de dados para poder executar. Estes dados devem ser fornecidos pelo código Java.

Suponha que você tem um script que converte uma temperatura dada em graus Fahrenheit para uma temperatura em graus Celsius, definido assim:

  var tc = (tf - 32) * 5 / 9;
  println(tc);

Perceba que este script só pode ser executado se, ao ser chamado, tf (que representa a temperatura em graus Fahrenheit) tenha um valor definido. Para definir um valor para esta variável, usamos o método put():

  engine.put("tf", 80);
  engine.eval(new FileReader("script.js"));

O papel do método put() é atrelar ao script uma variável com um determinado valor. O primeiro parâmetro é uma String contendo o nome da variável e o segundo parâmetro indica o valor associado a ela.

Lendo Valores de Variáveis do Script

Assim como em algumas situações é preciso definir valores para variáveis de um script, às vezes também é necessário ler informações retornadas pelo script. Suponha que este mesmo script de conversão de temperatura seja agora definido desta forma:

  var tc = (tf - 32) * 5 / 9;

O script faz o cálculo e armazena o valor em tc. Depois que ele é executado, o código Java precisa ler o valor armazenado nesta variável para saber qual foi a resposta. Isto é feito com o método get():

  engine.put("tf", 80);
  engine.eval(new FileReader("script.js"));
  Double tc = (Double) engine.get("tc");

O método get() recebe como parâmetro a variável a ser acessada e retorna o seu valor. Neste caso, como o valor é um decimal, ele é tratado como um double na linguagem Java.

Chamando Funções do Script

A linguagem JavaScript permite a criação de funções, as quais podem ser chamadas pelo código Java. Por exemplo, suponha que o script é escrito assim:

  function convertToCelsius(tf) {
   var tc = (tf - 32) * 5 / 9;
   return tc;
  }

O código JavaScript define agora uma função chamada convertToCelsius, que recebe como parâmetro um temperatura em Fahrenheit e retorna uma temperatura em Celsius. Para fazer essa chamada, o seguinte código pode ser utilizado:

  engine.eval(new FileReader("script.js"));
  Invocable invocable = (Invocable) engine;
  Object resp = invocable.invokeFunction("convertToCelsius", 80);
  Double tc = (Double) resp;

Inicialmente, o já conhecido método eval() é chamado, a fim de executar o script que define a função. Depois um casting do objeto ScriptEngine para Invocable é realizado. Este passo é necessário para que o método invokeFunction(), que é chamado na sequência, possa ser utilizado.

O método invokeFunction() recebe como primeiro parâmetro o nome da função a ser chamada no script, neste caso convertToCelsius. Os próximos parâmetros correspondem aos parâmetros que devem ser passados para a função. Aqui apenas um parâmetro (80) é fornecido, pois a função só recebe um. Se a função recebesse mais parâmetros, eles teriam que ser informados também.

Ao fim da execução, invokeFunction() retorna o valor que a função retornou. Neste caso, o retorno é um valor decimal, portanto um casting para Double é necessário para obtermos o valor (invokeFunction() retorna um objeto do tipo Object).

Implementação de Interface

Um recurso interessante da Java Scripting API é a possibilidade escrever código em JavaScript que implementa uma interface definida no Java.

Para mostrar um exemplo disso na prática, suponha que você tem uma interface Calculo na sua aplicação definida da seguinte forma:

  public interface Calculo {
  public double calcular(Valores valores);
  }

Esta interface tem apenas um método chamado calcular(), que recebe como parâmetro um objeto do tipo Valores. Esta classe é definida assim:

  public class Valores {
  private double a;
  private double b;
  private double c;
  public double getA() {
  }
  public void setA(double a) {
  }
  public double getB() {
  }
  public void setB(double b) {
  }
  public double getC() {
  
  }
  public void setC(double c) {
  return a;
  this.a = a;
  return b;
  this.b = b;
  return c;
  this.c = c;
  }
  }
  

Imagine que a fórmula do cálculo, que usa os valores de a, b e c, varia com frequência. Então esta implementação é uma boa candidata a ser realizada em JavaScript. Precisamos criar um script que implemente a interface Calculo, fornecendo uma implementação concreta para o método calcular(). Este script pode ser escrito assim:

  function calcular(valores) {
   return valores.a + valores.b + valores.c;
  }
  

A implementação de interfaces em JavaScript é bastante simples. Basta definir funções que sejam compatíveis com os métodos definidos na interface. Neste caso, o método se chama calcular, recebe um parâmetro e retorna um valor numérico. É justamente esta implementação definida pela function. Os três valores são somados e o resultado é retornado.

Um detalhe interessante aqui é que o script usa as chamadas valores.a, valores.b e valores.c para acessar os atributos do objeto. Elas equivalem, em Java, às chamadas valores.getA(), valores.getB() e valores.getC().

Depois de preparado o script, o método getInterface() é usado no Java:

  engine.eval(new FileReader("script.js"));
  Invocable invocable = (Invocable) engine;
  Calculo calculo = invocable.getInterface(Calculo.class);

Novamente é preciso fazer o casting de engine para Invocable, a fim de que o método getInterface() seja chamado. Este método é responsável por retornar uma implementação da interface cujo tipo é informado como parâmetro, neste caso, Calculo.

Tendo uma instância de Calculo, calcular() já pode ser chamado. Veja um exemplo:

  Valores valores = new Valores();
  valores.setA(10);
  valores.setB(20);
  valores.setC(30);
  double d = calculo.calcular(valores);

Primeiro criamos um objeto do tipo Valores e depois atribuímos valores para os atributos a, b e c através dos métodos setters correspondentes. Depois chamamos o método calcular() no objeto e o resultado retornado será 60, que corresponde à soma dos três valores.

Se futuramente a fórmula do cálculo precisar ser alterada, basta ir até o arquivo do script e mudar a fórmula, e a aplicação vai passar a efetuar o cálculo de forma diferente.

Conclusão

Este artigo mostrou uma introdução à Java Scripting API, existente a partir do Java 6 e definida pela JSR 223. Foram mostrados exemplos de uso da API na integração de aplicações Java com códigos definidos em JavaScript, a fim de externalizar alguns aspectos da aplicação usando uma linguagem de script. Isto facilita o processo de manutenção, uma vez que as alterações no script são refletidas imediatamente pela aplicação sem que haja a necessidade de recompilação e redistribuição da mesma.

Referências

Carlos Tosin é instrutor oficial dos Cursos On-Line de Android e Java (assista uma vídeo-aula grátis) da Softblue, formado em Ciência da Computação pela PUC-PR, pós-graduado em Desenvolvimento de Jogos para Computador pela Universidade Positivo e Mestre em Informática na área de Sistemas Distribuídos, também pela PUC-PR. Trabalha profissionalmente com Java há 9 anos e possui 5 anos de experiência no desenvolvimento de sistemas para a IBM dos Estados Unidos, utilizados a nível mundial. Atua há mais de 2 anos com cursos e treinamentos de profissionais em grandes empresas. Possui as certificações da Sun SCJP, SCJD, SCWCD, SCBCD, SCEA, IBM SOA e ITIL Foundation.


Veja a relação completa dos artigos de Carlos Tosin