miércoles
feb122014
Publicado RelProxy v0.8, hot class reloader y scripting para Java y Groovy
Me llena de orgullo y satisfacción anunciar la primera versión oficial RelProxy v0.8
¿Qué es RelProxy?
RelProxy tiene tres principales funcionalidades:
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.
- 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.
- Proporciona un entorno de scripting a Java, incluyendo la posibilidad de ejecutar "shell scripts" en Java puro
- 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.
Reader Comments (6)
¿Tengo que escribir código para que una de mis clases sea recargable? ¿O hay alguna forma automática de que todas las clases de mi aplicación se recarguen?
RelProxy no es JRebel, no se mucho de JRebel, pero hasta donde se JRebel se ejecuta a través de un javaagent que instrumentaliza todas las clases que se cargan (no digo que lo haga sino que puede hacerlo). JRebel conoce los frameworks que usas por lo que sabrá que tiene que recargar y que no, por eso JRebel no es fácil de clonar y por eso vale lo que vale.
RelProxy es más manual, tienes que poner en una carpeta accesible a la aplicación web (por ejemplo bajo WEB-INF) las clases en código fuente que quieres poder recargar, dicha carpeta debería ser parte de las carpetas declaradas como código fuente de tu IDE (o Maven, o NetBeans-Ant, están documentados un par de ejemplos) para que se compilen normalmente los .class. Dicha carpeta con el código fuente formará parte del WAR o del deploy en general y debe configurarse RelProxy con dicha carpeta.
RelProxy recargará automáticamente las clases de dicha carpeta cuando detecta un cambio de código fuente, pero para que las clases nuevas formen parte de tu app se accederán a ellas a través de los proxies de los que habla el artículo.
RelProxy encaja muy bien con ItsNat porque la lógica visual es muy funcional, yo lo llamo "Stateless OOP", pero es posible que no sea factible en otros contextos, y como se dice más arriba puede no conseguirse ser toda tu aplicación "hot reloadable".
Esto le da contexto a cierta respuesta.
:o)
Felicitaciones, Y suerte con esto.
Nota aparte, y por curiosidad,
¿Porque una licencia Apache?
Un saludo,
efrigerio: ¿Porque una licencia Apache?"
Pues porque es la más usada, reconozco que también me gusta la LGPL
Un ejemplo para hacertelo tu mismo.
http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html#dynamicreloading
@batch4j
Lo que se muestra en ese enlace ( y además de ser bastante rudimentario), no toma en cuenta algunos pequeños detalles, como el ordenamiento en gradas del ClassLoader, su estructura de permisos, y el impacto que tiene una clase que se altera (ella y sus dependencias) varias veces durante el tiempo de ejecución.
Un saludo,