Publicado RelProxy v0.8, hot class reloader y scripting para Java y Groovy
miƩrcoles, febrero 12, 2014 at 8:51PM
jmarranz
Me llena de orgullo y satisfacción anunciar la primera versión oficial RelProxy v0.8
https://github.com/jmarranz/relproxy/

¿Qué es RelProxy?
RelProxy tiene tres principales funcionalidades:

  1. Un recargador en tiempo de ejecución de clases modificadas a partir de su código fuente tanto en Groovy como en Java. Una especie de JRebel pero menos ambicioso... y más barato.
  2. Proporciona un entorno de scripting a Java, incluyendo la posibilidad de ejecutar "shell scripts" en Java puro
  3. Soporte de JSR-223 Java Scripting API para el "lenguaje de script Java".

RelProxy nació para proporcionar recarga automática de clases a ItsNat pero en su evolución acabó convirtiéndose en una herramienta de propósito general.
1) Un recargador en tiempo de ejecución de clases modificadas a partir de su código fuente tanto en Groovy como en Java
Empecemos con Groovy.
Es sabido que Groovy es un lenguaje que permite la recarga dinámica de sus clases pero lo que muchos seguro que no saben es que Groovy es también un lenguaje compilado a bytecode, siendo la propia infraestructura de Groovy la que gestiona dicha compilación bajo demanda.
Consideremos que disponemos de clases en Groovy con objetos que necesitan registrarse en librerías Java a modo de listeners. El objeto implementando la interface Java requerida para para registrar dicho objeto como listener no deja de ser un objeto "normal" Java (aunque fue creado por Groovy) cuando es recibido por la librería Java que recibe el listener. Por mucho que Groovy sea capaz de recargar sus clases, el objeto registrado ya NO puede ser recargado.
Este punto es donde entra RelProxy a través de la utilidad llamada GProxy, a través de GProxy creamos un objeto java.lang.reflect.Proxy asociado al objeto original Groovy, tal que es el proxy el que suministramos a la librería Java. Cuando un método es llamado en el objeto java.lang.reflect.Proxy la llamada es interceptada y a través del engine de Groovy se chequea si ha cambiado la clase, si ha cambiado se recrea el objeto original con la nueva clase, así como las clases dependientes.
Sólo se recarga desde el objeto puente y su código relacionado, puede no ser todo el código fuente de la aplicación pero puede evitar montones de re-deploys por ejemplo en aplicaciones web.
Esto también se hace con código Java a través de la utilidad JProxy, en este caso es la propia librería la que chequea de forma temporizada si hay cambios en el código fuente compilando al vuelo las clases cambiadas y recargando las clases.
Ejemplo Java basado en ItsNat:
-----------------------
FalseDB db = new FalseDB();
ItsNatServletRequestListener listener = JProxy.create(new example.javaex.JProxyExampleLoadListener(db), ItsNatServletRequestListener.class);
docTemplate.addItsNatServletRequestListener(listener);
---------------------
El objeto JProxyExampleLoadListener pasa a ser recargable, así como sus dependencias excepto el código que se carga "fuera del proxy" tal y como el FalseDB, la clase JProxyExampleLoadListener (implementando ItsNatServletRequestListener) en un proyecto ItsNat es donde se programa la lógica de vista, si dicha clase y sus relacionadas son recargables hace que junto a la recarga automática de los templates de HTML puro, sea posible programar el diseño visual y la lógica visual sin necesidad de hacer redeploys, ni siquiera es necesario que el contexto de la aplicación web se recargue.
El ejemplo en Groovy es idéntico pero en Groovy, mostrando por otra parte Groovy como un lenguaje alternativo a ItsNat.
2) Entorno de scripting en Java, incluyendo la posibilidad de ejecutar "shell scripts" en Java puro
Gracias a la utilidad jproxysh es posible crear un archivo, por ejemplo llamado example_java_shell, con este contenido:
-------------
#!/usr/bin/env jproxysh
String msg = args[0] + args[1];
System.out.println(msg);
System.out.println("example_java_shell 1 ");
example.javashellex.JProxyShellExample.exec();
------------
Y poder ser ejecutado sin necesidad de compilación previa.
Nada impide que pueda ser una clase completa:
-------------
#!/usr/bin/env jproxysh
import example.javashellex.JProxyShellExample;
public class example_java_shell_complete_class
{
    public static void main(String[] args)
    {
        String msg = args[0] + args[1];
        System.out.println(msg);
        System.out.println("example_java_shell_complete_class 1 ");
        JProxyShellExample.exec();
    }
}
-------------
JProxyShellExample puede ser también una clase de la que sólo se dispone del código fuente.
Es más, puede ser una clase normal Java sin necesidad de compilar:
-------------
jproxysh example_normal_class.java "HELLO " "WORLD!"
-------------
Podemos ejecutar un trozo de código:
------------
jproxysh -c 'System.out.print("This code snippet says: ");' \
            'System.out.println("Hello World!!");'
------------
O una clase completa:
------------
jproxysh -c 'public class _jproxyMainClass_ { '  \
            ' public static void main(String[] args) { ' \
            '    System.out.print("This code snippet says: ");' \
            '    System.out.println("Hello World!!");' \
            '  }' \
            '}'
------------
Por último al invocar jproxysh sin parámetros:
-------------------
jproxysh
------------------
Tenemos un sencillo shell interactivo en donde podemos escribir y ejecutar código de forma progresiva.
En su aspecto de Java scripting ha de quedar claro que RelProxy NO crea un lenguaje nuevo similar a Java tal y como hace BeanShell, "simplemente" compila en memoria bajo demanda el código fuente (aunque opcionalmente puede salvar como .class).
3) Soporte de JSR-223 Java Scripting API para el "lenguaje de script Java"
 
Java como un lenguaje de "script" más:
---------------
JProxyScriptEngineFactory factory = JProxyScriptEngineFactory.create(jpConfig);
ScriptEngineManager manager = new ScriptEngineManager();
manager.registerEngineName("Java", factory);
manager.getBindings().put("msg","HELLO GLOBAL WORLD!");
ScriptEngine engine = manager.getEngineByName("Java");
Bindings bindings = engine.createBindings();
bindings.put("msg","HELLO SCOPE WORLD!");
StringBuilder code = new StringBuilder();
code.append( " javax.script.Bindings bindings = context.getBindings(javax.script.ScriptContext.ENGINE_SCOPE); \n");
code.append( " String msg = (String)bindings.get(\"msg\"); \n");
code.append( " System.out.println(msg); \n");
code.append( " bindings = context.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE); \n");
code.append( " msg = (String)bindings.get(\"msg\"); \n");
code.append( " System.out.println(msg); \n");
code.append( " example.javashellex.JProxyShellExample.exec(engine); \n");
code.append( " return \"SUCCESS\";");
String result = (String)engine.eval( code.toString() , bindings);
System.out.println("RETURNED: " + result);
-----------

 
Espero que te sea útil.
Article originally appeared on javaHispano (http://www.javahispano.org/).
See website for complete article licensing information.