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);
        }
    }
}


Comments:

Hi Paul,
We're using 0.8-ea and are simultaneously playing around with JavaRebel. The example you posted about using Jersey and JavaRebel seems interesting, but a tad unrealistic. Do you have any idea as to how one might go about doing this with a Jersey and Spring approach?

Posted by James on July 15, 2008 at 09:06 AM CEST #

Hi James,

[just back from a 2 week holiday]

Unrealistic in what respect, the brute force approach? or that it is necessary to have some deployment code? or that it is using the LW HTTP server?

One could extend the Jersey spring servlet:

http://download.java.net/maven/2/com/sun/jersey/jersey-spring/

to register the JavaRebel reload listener. I can supply more details if you wish.

Posted by Paul Sandoz on July 21, 2008 at 04:47 AM CEST #

It would be nice to hear how we can force JavaRebel to do the job for the Jersey with Spring. For me it does reload Spring beans and other clasess, and JSPs fine, but JavaRebel's classes are not reloaded (well, I do not know if they are _reloaded_, but my changes do not appear until further re-deploy).
Thanks!

Posted by Olexiy Prokhorenko on June 15, 2009 at 06:48 PM CEST #

It would be nice to hear how we can force JavaRebel to do the job for the Jersey with Spring. For me it does reload Spring beans and other classes, and JSPs fine, but JavaRebel's classes are not reloaded (well, I do not know if they are _reloaded_, but my changes do not appear until further re-deploy).
Thanks!

Posted by Olexiy Prokhorenko on June 15, 2009 at 06:49 PM CEST #

@Olexiy,

When you say "but JavaRebel's classes are not reloaded" do you mean "but Jersey's classes are not reloaded" ?

IIUC if you do not make a signature change to a class or add/delete a class then things should work without having to hook up Jersey reloading. Reloading is required because Jersey caches information obtained via reflection.

It has been a while since i tried JavaRebel with Jersey so things might have changed.

How are you using JavaRebel with Jersey?

Perhaps we can communicate on the Jersey users list?

mailto:users@jersey.dev.java.net

Paul.

Posted by Paul Sandoz on June 16, 2009 at 04:46 AM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
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