Wednesday Apr 16, 2008

JavaRebel and Jersey: Take 2

Since i last played with JavaRebel it has been through one or more updates and now has an SDK. It is also been a while since i blogged, from the graph generated by Marc you should be able to determine why.

Finally i managed to get some time to revisit JavaRebel (many thanks to Jevgeni Kabanov for updating me on the progress of JavaRebel and sending pointers to relevant information). This entry explains how to dynamically reload the Jersey deployed web application in response to callbacks from JavaRebel when in detects changes to class files. Because Jersey caches runtime information about classes, for performance reasons, it needs to be informed to update information about classes that have changed. So adding a new HTTP method or a new resource class can be detected.

The code at the end of the blog shows a complete example that can work with the latest build of Jersey 0.7, JavaRebel 1.1 M3 and the JavaRebel SDK 1.1 M3. 

To connect JavaRebel to Jersey we need to implement two interfaces, the JavaRebel inteterface ReloadListener and the Jersey interface ContainerNotifier:

01 private static class Reloader implements ContainerNotifier,
02         ReloadListener {
03     private final List<ContainerListener> ls;
04
05     private final ClasspathResourceConfig rc;
06
07     public Reloader(ClasspathResourceConfig rc) {
08         ls = new ArrayList<ContainerListener>();
09         this.rc = rc;
10     }
11
12     public void addListener(ContainerListener l) {
13         ls.add(l);
14     }
15
16     public void reloaded(Class arg0) {
17         rc.reload();
18         for (ContainerListener l : ls) {
19             l.onReload();
20         }
21     }
22 }

The method reloaded on line 16 will get invoked every time JavaRebel detects a change to a Java class. The method addListener will get called by a Jersey container that wants to listen to container-based notifications. All containers shipped with Jersey will add a ContainerListener to a ContainerNotifier (if one is present, see later). When the reloaded method is invoked the class path is rescanned for classes then each listener registered with the container notifier will notified by an invocation of the onReload method (see line 19).

Next the ReloadListener and the ContainerNotifier need to be registered:

01 ClasspathResourceConfig rc = new ClasspathResourceConfig();
02 Reloader r = new Reloader(rc);
03       
04 rc.getProperties().put(
05     ResourceConfig.PROPERTY_CONTAINER_NOTIFIER, r);
06 ReloaderFactory.getInstance().addReloadListener(r); 
07
08 HttpServer s = HttpServerFactory.create("http://localhost:9999/",
09     ContainerFactory.createContainer(HttpHandler.class, rc));
10 s.start();

Lines 1 and 2 create the resource configuration (that scans class paths) and creates the reloader as previously shown. Lines 4 and 5 register the ContainerNotifier with Jersey by adding a property to the resource configuration. Line 6 registers the ReloadListener with JavaRebel. Finally lines 8, 9 and 10 start the Light Weight HTTP Server.

So if we configure to use JavaRebal, build and run the app, then change the code of the simple web application, recompile while still running, then Jersey will update accordingly.

I think the general concept works OK but it does require some finessing as currently it could be described as a rather brutish approach. A reload will occur for every class that has changed so Jersey will perform unnecessary reloads. I don't know how JavaRebel works but it may be useful to provide reloading within an a scope of "startReload" and "endReload". Furthermore Jersey needs to know about classes that have been removed and not just those that have been modified or added (i have not tested what JavaRebel does when classes are removed). On the Jersey side i think we can refine this to support add/update or deletion of classes. For resource classes this is probably fairly easy. For provider classes it is a little more work.

I could imagine such reloading capability might be useful not just for rapid development but for dynamic deployment of additional root resource classes as and when they are required.
 



import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.ws.rest.api.container.ContainerFactory;
import com.sun.ws.rest.api.container.httpserver.HttpServerFactory;
import com.sun.ws.rest.api.core.ClasspathResourceConfig;
import com.sun.ws.rest.api.core.ResourceConfig;
import com.sun.ws.rest.spi.container.ContainerListener;
import com.sun.ws.rest.spi.container.ContainerNotifier;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import org.zeroturnaround.javarebel.ReloadListener;
import org.zeroturnaround.javarebel.ReloaderFactory;

public class Main {
    @Path("/{id}")
    public static class RebelResource {
        @GET
        public String getOne(@PathParam("id") int id) {
            return Integer.toString(id) + "x";
        }
    }

    private static class Reloader implements ContainerNotifier,
            ReloadListener {
        private final List<ContainerListener> ls;

        private final ClasspathResourceConfig rc;

        public Reloader(ClasspathResourceConfig rc) {
            ls = new ArrayList<ContainerListener>();
            this.rc = rc;
        }

        public void addListener(ContainerListener l) {
            ls.add(l);
        }

        public void reloaded(Class arg0) {
            rc.reload();
            for (ContainerListener l : ls) {
                l.onReload();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ClasspathResourceConfig rc = new ClasspathResourceConfig();
        Reloader r = new Reloader(rc);

        rc.getProperties().put(
            ResourceConfig.PROPERTY_CONTAINER_NOTIFIER, r);

        ReloaderFactory.getInstance().addReloadListener(r);

        HttpServer s = HttpServerFactory.create("http://localhost:9999/",
                ContainerFactory.createContainer(HttpHandler.class, rc));
        s.start();

        try {
            System.in.read();
        } finally {
            s.stop(0);
        }
    }
}


Friday Jan 04, 2008

JavaRebel and Jersey

I have been playing with JavaRebel (thanks for the tip Kohsuke!) and Jersey. JavaRebel:

reloads changes to Java classes on-the-fly without redeploy or restart including new methods and fields. It is a generic solution that works for standalone Java applications as well as application servers

I opened the HelloWorld example provided in the Jersey distribution and modified the run.jvmargs property in the nbproject/project.properties file to be:

run.jvmargs=-noverify -javaagent:<dir>/javarebel.jar

ran the project and verified with curl the response to a GET request.

Then i modified the String returned by the HTTP GET method of the HelloWorldResource recompiled the project and verified the response to the GET request had changed. (I wish NetBeans had an automatic compile option.)

It is also possible to integrate with Glassfish (although i have not tried it). Add the following snippet of XML as a child of the <java-config> element in a domain.xml:

<jvm-options>-noverify</jvm-options>
<jvm-options>-javaagent:<dir>/javarebel.jar</jvm-options>

A very nice and easy integration. Clever stuff! I wonder how it is done...

Unfortunately it is not possible to modify the annotations, method signatures, add new methods, or add new resource classes. Jersey creates, for performance reasons, a runtime model of a resource so it does not have to analyze the annotations using Java reflection every time. Jersey has no way of knowing if a resource class has been created, modified or deleted. (I suppose it is the same for any framework that caches information about classes, for example JAXB.) 

If there was a way to detect changes i think it would be easy to modify Jersey to update its runtime model accordingly (an API for listening to such changes seems useful independently of JavaRebel). And then it would make a rather nice rapid development platform for Jersey development.

About

sandoz

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today