A JRebel plugin is nothing more than a small .jar-file containing at least a class that implements the Plugin interface of the JRebel SDK, and a special manifest entry telling JRebel the name of that class. It is highly recommended to start creating any plugin project from the provided template.

The key API in plugin development is the JRebel SDK which provides means to communicate with the JRebel private APIs. Your plugin code can also make use of whatever else available on the JVM you are running in. For example, if you are developing a plugin for Struts2 framework (that will get activated only if Struts2 really is available in the JVM), you can safely import and use classes from Struts2 in your plugin (without providing them yourself).

The Plugin interface you need to implement looks like that:

public interface Plugin {
  void preinit();
  boolean checkDependencies(ClassLoader cl, ClassResourceSource crs);
  String getId();
  String getName();
  String getDescription();
  String getAuthor();
  String getWebsite();
}

The most important methods here are:

  public boolean checkDependencies(ClassLoader classLoader, ClassResourceSource classResourceSource) {
    return classResourceSource.getClassResource( "org.zeroturnaround.javarebel.sdkDemo.AbstractCanvas"!= null;
  }

Refer to JRebel SDK API reference for additional details.

2.1. The demo application

To demonstrate working with JRebel plugins, we have created a very simple Swing desktop application (get the source). It just draws three Image objects on a Canvas object, which is in turn placed inside a JFrame object and displayed. It looks like that:

.. and its class hierarchy looks like this:

As discussed above, a typical situation for writing a JRebel plugin is when you want to provide JRebel integration for a framework maintained by a third party. Let's imagine that the class AbstractCanvas is our third-party "framework" that we want to provide better JRebel integration for. Therefore, we have a restriction that we can't directly edit its code (if we had full control over the code in AbstractCanvas, we could just add our modifications directly into it and problem solved). Let's further imagine that FlagsCanvas is our "application" that we are developing. Our goal is to build a plugin that provides better JRebel support for hotloading any client code of the AbstractCanvas "framework" (i.e. while modifying the FlagsCanvas class, in our case).

Start off by download the demo app and making yourself familiar with it. To run it with JRebel, you need to edit the file build.properties and make jrebel.jar.path point to jrebel.jar on your local machine. There are three Ant targets that execute the application:

The code that controls where the flags get painted to is in these two methods of FlagsCanvas.java:

    private void init() {
        estoniaX      =  65
        estoniaY      =  50;
        switzerlandX  = 400;
        switzerlandY  =  50;
    }
    
    
    public void paint(Graphics g) {
        System.out.println( " Painting the canvas ..");
        
        g.drawImage(switzerlandImg, switzerlandX, switzerlandY, this);
        g.drawImage(estoniaImg,     estoniaX,     estoniaY,     this);
        g.drawImage(iranImg,        650,          50,           this);
    }

Execute the app with JRebel but without the plugin. The paint() method gets called by Swing every now-and-then when some event happen with the frame (it gains or looses focus, etc). Try modifying the coordinates of the flags and see what happens (of course, something needs to re-build your .class files after editing them. It's best to configure your IDE to do that automatically whenever you save your source files). When you re-focus to the demo-application, paint() gets called and the Iranian flag should indeed move if you changed its coordinates in the Java code. The other flags demonstrate now reaction whatsoever. That's because their locations are controlled by the instance variables of the FlagsCanvas class that get initialized by the init() method when a new instance of the class is created. So, although you might have changed their values in the init() method, the values in the memory remain the same as nobody is calling that method after the instance is already created.

2.2. Class bytecode processors (CBPs)

So the class reloading works, but this doesn't do the whole job here as the in-memory state of the application doesn't get updated. Notice that all that we would need to do to fix that is make an additional call to init() whenever we call paint(). The JRebel SDK fortunately provides suitable tools for the job. Whenever you need to modify the behavior of third-party code before it runs in your JVM, you'll use the so-called class bytecode processors (CBPs), extending the JavassistClassBytecodeProcessor class. When set up properly, the CBPs intercept the initial class loading in the JVM and load the third-party classes with our modifications already in place. Let's see what a CBP looks like:

public class ReloadAbstractCanvasStateCBP extends JavassistClassBytecodeProcessor {

  public void process(ClassPool cp, ClassLoader cl, CtClass ctClassthrows Exception {
    
    LoggerFactory.getInstance().echo("Patching the AbstactCanvas class ..");
    try {
      CtMethod paintMethod = ctClass.getDeclaredMethod("repaint");
      paintMethod.insertBefore("org.zeroturnaround.javarebel.integration.pluginTemplate.DemoAppConfigReloader.reinitialize($0);");
      
    catch (NotFoundException e) {
      LoggerFactory.getInstance().error(e);
    }
  }
}

What this CBP does is it inserts an extra statement into the top of the repaint() method of the AbstractCanvas class, transforming the control flow to a public static method implemented in our plugin. The current instance is passed as an argument ($0 stands for this in Javassist code-strings). The method reinitialize() implemented in our plugin can now make arbitrary customizations and then hand the control back to AbstractCanvas#paint(). Currently, it all it does is just calling the init() method with Reflection API.

Notice that here it would actually made more sense to call the init() method right away:

      paintMethod.insertBefore("init()");

That approach might be reasonable when the customization you need to do is trivial. Anyhow, as things get any more complicated, forwarding the call might be more comfortable than maintaining larger chunks of code inside Java strings.

The CBP also has to be registered in the plugin class for things to work:

public class PluginTemplate implements Plugin {

  public void preinit() {

    // Register the CBP
    Integration i = IntegrationFactory.getInstance();
    ClassLoader cl = PluginTemplate.class.getClassLoader();
    i.addIntegrationProcessor(cl, "org.zeroturnaround.javarebel.sdkDemo.AbstractCanvas"new ReloadAbstractCanvasStateCBP());
    
    // ..
  }

  // ..
}

Integration is the class through which CBPs can be set up; instance of it is acquired through IntegrationFactory .. sounds simple enough.

A word of warning: be very careful with referencing the classes you are about to patch in the CBP code itself (or other code that is called by the CBP). Remember: the CBP is all about intercepting a class before it gets loaded. If your CBP now tries to do something with the very same class, JVM would have to load it to do it. This would create a cyclic dependency, blowing the thing!

2.3. Class reload listeners

If you are prepared to spend some time, I recommend you take the demo application and the plugin template, reduce the plugin to what we have done so far (basically just cutting the rest of the preinit() method), re-build the plugin jar and replace the old plugin with the new one in demo application's ./lib directory. If you are not, you'll just have to believe me for now. Our AbstractCanvas-to-JRebel integration still has a small flaw. Namely, if you change the coordinates of flags and recompile the code, repainting of the canvas happens when Swing wants it to be repainted, not when you actually changed anything. (Try splitting your desktop between Eclipse and the application frame and editing the code in Eclipse while you application is inactive. Whatever you do with the code, Swing won't repaint until you focus back to the demo app.) It is obviously not a very big problem, as Swing wants to repaint things almost always when you actually do anything with the app, but let's still try to fix that – at least for the educational purpose.

This is an ideal moment for introducing reload listeners. The JRebel SDK provides an API to register listeners that get notified when JRebel reloads anything. By creating an implementor of the ClassEventListener interface and registering it through Reloader, we can implement custom reactions to class reload events. This is exactly what we'll do:

  public void preinit() {
    // ..

    // Set up the reload listener
    ReloaderFactory.getInstance().addClassReloadListener(
      new ClassEventListener() {
        public void onClassEvent(int eventType, Class klass) {

          try {
            Class abstractCanvasClass = Class.forName( "org.zeroturnaround.javarebel.sdkDemo.AbstractCanvas");
          
            // Check if it is child of AbstractCanvas
            if (abstractCanvasClass.isAssignableFrom(klass)) {
              DemoAppConfigReloader.repaint();
              LoggerFactory.getInstance().echo("Repainted the canvas");
            }
            
          catch (Exception e) {
            LoggerFactory.getInstance().error(e);
          }
        }

        // ..
      }
    );

  }

What this chunk of code does is actually simple: if we see that a class has been reloaded, and it happens to be a subclass of AbstractCanvas, we force the repainting of the canvas (without waiting for Swing to do it at some later time). The repainting itself is implemented in method DemoAppConfigReloader.repaint().

Now our AbsractCanvas JRebel integration is starting to look much better. Our plugin added the following functionality:

We demonstrated some cornerstones of the JRebel SDK API: Some basic things can already be done with this knowledge, and you are of course encouraged to browse the rest of the API to make yourself familiar with the rest of its possibilites – to create better JRebel integration plugins!