I'm the father of ItsNat AJAX Java web framework, JNIEasy, LAMEOnJ, JEPLayer and JEPLDroid libraries (and very old stuff like XPDOM), supporter of the Single Page Interface (SPI) paradigm, writer of the SPI manifesto and currently Android native guy. Jose Maria has posted 28 posts at DZone. You can read more from them at their website. View Full User Profile

RelProxy: A Hot Class Reloader and Scripting for Java and Groovy

02.15.2014
| 10369 views |
  • submit to reddit

What is RelProxy?

RelProxy has three principal features:

1) A class reloader in runtime from source code in Groovy or Java. Similar to JRebel but not so sophisticated... and cheaper.

2) A scripting environment for Java as a scripting language, including "shell scripts" coded in pure Java.

3) JSR-223 Java Scripting API  support for "the scripting language named Java."

RelProxy was born to provide automatic class reload to ItsNat, a Java based web framework, for code in Groovy, it became a general purpose tool including Java support.


1) A class reloader in runtime from source code in Groovy or Java.

Let's go first with Groovy. You know Groovy is a dynamic language that supports built-in hot class reload. Maybe you don't know Groovy is a bytecode based language. In this case, Groovy engine compiles Groovy classes on demand. Think we have several Groovy classes with object instances going to be registered into some Java library as listeners implementing some interface. The Groovy object implementing the Java interface is in memory a "normal" Java object (in spite it was created by Groovy), then this object is received by the Java library when registering. In spite of Groovy can reload classes, there is no way to reload the Groovy object registered.

This is when RelProxy makes its job through a utility named GProxy. With GProxy, we create a  java.lang.reflect.Proxy and pass this proxy instead of the original Groovy object. When a method is called to java.lang.reflect.Proxy, the method call is intercepted by GProxy an through Groovy engine RelProxy checks whether some class has changed, when changed the source code of the class of the original object or other dependent class, the original object is re-created based on the new class, the same dependent classes.

Only the bridge object and related/dependent code is reloaded, maybe not everything, but it may avoid many re-deploys for instance in web applications. The same is applied to Java code with the utility JProxy, in this case is RelProxy which timely checks source code changes, compiling changed classes on the fly and reloading all classes with a new ClassLoader. The following is a Java example based on ItsNat:
FalseDB db = new FalseDB();
ItsNatServletRequestListener listener = 
   JProxy.create(new example.javaex.JProxyExampleLoadListener(db), 
      ItsNatServletRequestListener.class);
docTemplate.addItsNatServletRequestListener(listener);
The object JProxyExampleLoadListener is reloadable, the same its dependencies but FalseDB because is created "outside of the proxy environment". In case of an ItsNat web project, the class JProxyExampleLoadListener (implementing ItsNatServletRequestListener) is where we code view logic, whether this class and related/dependent classes can be hot reloaded and also pure HTML templates are reloaded by ItsNat when changed, we have a development environment (also in production?) where re-deployments are drastically reduced because view design and view logic are hot reloadable. The Groovy example is exactly the same but in Groovy (Groovy is an alternative language for ItsNat projects).
2) Scripting environment for Java as "scripting language", "shell scripts" based on pure Java Thanks to jproxysh utility you can create an archive, for instance with name example_java_shell, with this content:
#!/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();
and be executed with no previous compilation. It can be a complete Java class:
#!/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 can also be a Java class with just the source code.
Also it could be a normal Java class, again with no need of compilation (JavaSE projects with no compilation):
jproxysh example_normal_class.java "HELLO " "WORLD!"
We can execute a code snippet:
jproxysh -c 'System.out.print("This code snippet says: ");' \
                   'System.out.println("Hello World!!");'
Or a complete class:
jproxysh -c 'public class _jproxyMainClass_ { '  \
            ' public static void main(String[] args) { ' \
            '    System.out.print("This code snippet says: ");' \
            '    System.out.println("Hello World!!");' \
            '  }' \
            '}'
Finally invoking jproxysh with no param:
jproxysh
Starts a simple interactive shell, where we can write and execute Java code progressively. Must be noted RelProxy does NOT create a new language similar to Java, such as BeanShell does, RelProxy just compiles on the fly in memory (optionally can save .class files).


3) JSR-223 Java Scripting API  support for the scripting language "Java" Java is just another "script" language supported:
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);
ENJOY!!
Published at DZone with permission of its author, Jose Maria Arranz.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)