Earthly Powers
- All
- Fast Infoset
- General
- Java
- REST
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);
}
}
}
Posted at 06:07PM Apr 16, 2008 by Paul Sandoz in REST | Comments[2]
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 04:06 PM 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 11:47 AM CEST #