5 cosas que no sabía sobre...la API Java Scripting

Una forma más fácil de hacer scripts en la plataforma de Java

El lenguaje de Java™ es más de lo que necesita para algunos proyectos, pero los lenguajes de scripts tienen fama de ser insuficientes en cuanto a rendimiento. Descubra cómo la API Java Scripting (javax.script) entrega lo mejor de ambos mundos, al permitirle invocar scripts de sus programas de Java y viceversa.

Ted Neward, Director, Neward & Associates

Ted NewardTed Neward es el director de Neward & Associates, donde hace consultoría, es mentor, enseña y hace presentaciones sobre Java, .NET, XML Services y otras plataformas. Vive en Seattle, Washington.



12-11-2012

Sobre esta serie

¿Así que piensa que sabe de programación en Java? La realidad es que la mayoría de los desarrolladores sólo conocen las bases de la plataforma de Java, aprendiendo sólo lo necesario para realizar el trabajo. En esta serie, Ted Neward explora las funcionalidades principales de la plataforma de Java para descubrir hechos poco conocidos que podrían ayudarle a solucionar incluso los más complicados retos de programación.

Muchos desarrolladores de Java actualmente están interesados en el uso de lenguajes de scripts en la plataforma de Java, pero utilizar un lenguaje dinámico que ha sido compilado en código de bytes de Java no siempre es posible. En algunos casos, es más rápido y más eficiente simplemente hacer un script de partes de una aplicación de Java o llamar los objetos de Java particulares que necesita desde un script.

Ahí es donde entra javax.script . La API Java Scripting introducida en Java 6, elimina la brecha entre los prácticos lenguajes de scripts pequeños y el robusto ecosistema de Java. Con la API Java Scripting, es posible rápidamente integrar virtualmente cualquier lenguaje de scripts con su código de Java, el cual abre sus opciones considerablemente al solucionar problemas pequeños de alguien más.

1. Ejecutando JavaScript con jrunscript

Desarrolle habilidades de este tema

Este contenido es parte de un knowledge path progresivo para avanzar en sus habilidades. Vea Conviértase en un desarrollador de Java

Cada nuevo release de la plataforma de Java ofrece un nuevo conjunto de herramientas de línea de comandos incrustado en el directorio bin del JDK. Java 6 no fue la excepción, y jrunscript no es una adición pequeña para las utilidades de la plataforma de Java.

Considere el problema básico de grabar un script de línea de comandos para la supervisión del rendimiento. La herramienta prestará jmap (introducido en el artículo anterior de la serie) y lo ejecutará cada 5 segundos en un proceso de Java, para así obtener una sensación que nos diga cómo se está ejecutando el proceso. Normalmente, los scripts de shell de línea de comandos funcionarán, pero en este caso la aplicación del servidor está desplegada en distintas plataformas, incluyendo Windows® y Linux®. Los administradores del sistema verán que intentar escribir los scripts de shell que se ejecutan en ambas plataformas es un dolor de cabeza. La solución usual es grabar un archivo de proceso por lotes de Windows y un script de shell de UNIX® y simplemente mantener los dos en sincronía con el paso del tiempo.

Pero, como sabe cualquier lector de El Programador Pragmático , esta es una violación horrenda del principio DRY (no te repitas) y es un caldo de cultivo para errores y defectos. Lo que realmente nos gustaría hacer es escribir algún tipo de script de SO neutro que pueda ejecutarse en todas las plataformas.

El lenguaje de Java es de plataforma neutra, por supuesto, pero este no es realmente el caso para un lenguaje de "sistema". Lo que necesitamos es un lenguaje de scripts — como JavaScript, por ejemplo.

El Listado 1 comienza con un shell básico de lo que queremos:

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

Muchos, si no es que la mayoría, de los desarrolladores de Java ya conocen JavaScript (o ECMAScript; JavaScript es un dialecto de ECMAScript propiedad de Netscape) gracias a nuestras interacciones forzadas con los navegadores web. La pregunta es, ¿cómo ejecutaría este script un administrador del sistema?

La solución, por supuesto, es la utilidad jrunscript que viene con el JDK, como se muestra en el Listado 2:

Listado 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!
...

Tome en cuenta que usted también podría usar un bucle for para ejecutar el script un número determinado de veces antes de salir. Básicamente, jrunscript le permite hacer casi todo lo que normalmente haría con JavaScript. La única excepción es que el entorno no es un navegador, así que no hay un DOM. Las funciones y objetos de máximo nivel disponibles son, por lo tanto, un poco distintas.

Ya que Java 6 viene con el motor de Rhino ECMAScript como parte del JDK, jrunscript puede ejecutar cualquier código de ECMAScript que sea alimentado en él, ya sea desde un archivo (como se muestra aquí) o en un entorno shell más interactivo llamado un REPL ("Read-Evaluate-Print-Loop"). Simplemente ejecute jrunscript por sí mismo para acceder al shell de REPL.


2. Accediendo a objetos de Java desde un script

Poder escribir código de JavaScript/ECMAScript está muy bien, pero no queremos tener que recrear todo lo que usamos en el lenguaje de Java desde cero — eso iría en contra de nuestro propósito. Afortunadamente, cualquier cosa que use los motores de la API Java Scripting tiene acceso a todo el ecosistema de Java ya que, en esencia, todo sigue siendo código de bytes de Java. Entonces, regresando al problema anterior, podemos lanzar procesos desde la plataforma de Java utilizando la llamada Runtime.exec() tradicional, como se muestra en el Listado 3:

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

El array arguments es la referencia incorporada estándar de ECMAScript para los argumentos pasados a esta función. En el caso del entorno de script de máximo nivel, esta es el array de argumentos pasada al script mismo (los parámetros de la línea de comandos). Así, en el Listado 3, el script espera a un argumento que contenga el VMID del proceso de Java para correlacionar.

Alternativamente, podemos aprovechar el hecho de que jmap es una clase de Java y sólo llama su método main() , como en el Listado 4. Este enfoque elimina la necesidad de "conectar" las secuencias in/out/err del objeto Process .

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

La sintaxis Packages es una notación de Rhino ECMAScript utilizada para referirse a un paquete de Java fuera de los paquetes java.* centrales ya configurados en Rhino.


3. Llamando en los scripts desde el código de Java

Llamar objetos de Java desde un script sólo es la mitad de la historia: el entorno de scripts de Java también proporciona la capacidad de invocar scripts desde dentro del código de Java. Hacer esto sólo requiere crear una instancia de un ScriptEngine, después cargar el script y evaluarlo, como se muestra en el Listado 5:

Listado 5. Haciendo scripts en la plataforma de 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();
        }
    }
}

El método eval() también puede operar en una Cadena directa, de forma que el script no necesita venir de un archivo en el sistema de archivos — puede venir de una base de datos, del usuario o incluso ser creada dentro de la aplicación con base en las circunstancias y en las acciones del usuario.


4. Enlazando objetos de Java en el espacio del script

Simplemente invocar un script no es suficiente: los scripts frecuentemente quieren interactuar con objetos creados desde dentro del entorno de Java. En estos casos, el entorno host de Java debe crear objetos y enlazarlos de forma que sean sencillos de localizar y usar para el script. Esta es una tarea para el objeto ScriptContext mostrado en el Listado 6:

Listado 6. Enlazando 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();
        }
    }
}

Acceder al objeto enlazado es sencillo — el nombre del objeto enlazado es introducido en el nivel del script como un miembro del espacio de nombres global, así que utilizar el objeto Person de Rhino es tan fácil como se muestra en el Listado 7:

Listado 7. ¿Quién escribió este artículo?
println("Hello from inside scripting!")

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

Como es posible ver, las propiedades estilo JavaBeans son reducidas a accesores de nombres directos, como si fueran campos.


5. Compilando scripts frecuentemente utilizados

La desventaja de los lenguajes de scripts siempre ha sido el rendimiento. La razón es que, en la mayoría de los casos, el lenguaje de scripts es interpretado "al vuelo" y pierde tiempo y ciclos de CPU al tener que analizar y validar el texto como es ejecutado. Muchos lenguajes de scripts ejecutándose en la JVM al final transforman el código entrante en código de bytes de Java, al menos la primera vez que el script es analizado y validado; esta compilación al vuelo es arrojada cuando el programa de Java se concluye. Mantener los scripts utilizados frecuentemente en forma de código de bytes nos daría una ventaja de rendimiento considerable.

Podemos hacer esto en una forma natural y significativa con la API Java Scripting. Si el ScriptEngine retornado implementa la interfaz Compilable , entonces el método de compilación en esa interfaz puede ser utilizado para compilar el script (pasado como una Cadena o como un Lector) en una instancia CompiledScript , que después puede ser utilizada para eval() el código compilado una y otra vez, con distintos enlaces. Esto es mostrado en el Listado 8:

Listado 8. Compilando el 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();
        }
    }
}

En la mayoría de los casos, la instancia CompiledScript debe alojarse en alguna parte en almacenamiento a largo plazo (servlet-context, por ejemplo) para evitar la recopilación del mismo script una y otra vez. Sin embargo, si el contenido del script cambiara, debe crear un nuevo CompiledScript para reflejar el cambio; una vez compilado, el CompiledScript ya no ejecuta el contenido del archivo del script original.


En conclusión

La API Java Scripting es un enorme paso hacia la extensión del alcance y la funcionalidad de los programas de Java, y ofrece las ganancias de productividad asociadas con los lenguajes de scripts en el entorno de Java. Junto con jrunscript— que obviamente no es un programa demasiado complejo de escribir —javax.script da a los desarrolladores de Java los beneficios de los lenguajes de scripts como Ruby (JRuby) y ECMAScript (Rhino) sin tener que abandonar el ecosistema y la escalabilidad del entorno de Java.

A continuación en la serie de las 5 cosas: JDBC.

Recursos

Aprender

Comentar

  • Participe de la comunidad My developerWorks. Conéctese con otros usuarios de developerWorks mientras explora blogs, foros, grupos y wikis impulsados por desarrolladores.

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=tecnologia Java
ArticleID=845259
ArticleTitle=5 cosas que no sabía sobre...la API Java Scripting
publish-date=11122012