Zero turn-around is one of the selling points of PHP, Ruby, Python and other interpreted languages. Turns out it also works out-of-the-box with Aranea and here we'll describe how we did it, what are the limitations and show off the compositional nature of Aranea. If you just want to get it to work see the reference manual entry on the topic.
Zero turn-around usually refers to the way changes made to the code are immediately visible in most interpreted languages. Indeed, since there is no compilation and almost no deployment, the time from making a change to seeing it in the browser is a fast as pressing Ctrl + S, Alt + Tab and F5. The situation in the JEE world is different -- although compilation is relatively fast, together with deployment, cache reloads and framework initialization it might easily take up to a minute to see a change propagate in a large application.
The problem is that JVM spec clearly states that we can't just reload code of a single class after it has been loaded into the JVM. HotSpot JVM offers some way by allowing to replace the code of the methods, but so far it forbids to anyhow alter structure and signatures, so it's only of limited use. What we can do is to load classes using a particular (possibly our own) classloader, and then let it to be garbage collected among with all loaded classes. Then next time an instance is created the class will be loaded anew.
However even this won't give us the desired result, as any existing instance of a class will still hold on to its previous definition (with the old classloader) and now we might actually have two conflicting class definitions in two different classloaders which can lead to all sorts of trouble. So we also need to somehow reload the object state in a new classloader.
Turns out that we can archive this with serialization -- we just need to modify the ObjectInputStream to take a classloader parameter and load the instances using its classes. Thus what we have to do is:
- Load the classes using our classloader.
- Serialize the state of the instances
- Drop the old classloader and create a new one
- Deserialize state of the instances in the new classloader
Of course if some objects cannot be serialized (e.g do not implement Serializable or contain non-serializable fields) the whole scheme fails, since we cannot preserve their state.
Before we get to the actual Aranea stuff let's try to implement the reloading procedure, that takes an object and reloads it using a fresh classloader. We know it should work through serialization, so it should look something like this:
JAVA:
-
...
-
-
-
return deepCopy(newClassLoader(), child);
-
}
-
...
deepCopy should just serialize and deserialize the object. There is one catch though -- it should also resolve the classes using our own classloader, so we have to make a subclass of ObjectInputStream:
JAVA:
-
...
-
private static class ReloadingObjectInputStream
-
-
-
-
public ReloadingObjectInputStream(
-
-
-
-
super(in);
-
this.cl = cl;
-
}
-
-
/*
-
* This method is used to resolve the classes
-
* when creating object instances.
-
*/
-
-
-
-
return cl.loadClass(name);
-
}
-
}
-
...
The deepCopy() method itself is straightforward and we could have used Apache SerializationUtils methods if it weren't for the custom ObjectInputStream:
JAVA:
-
...
-
-
-
-
//Serialize to a byte array
-
-
-
-
-
try {
-
out.writeObject(original);
-
}
-
finally {
-
out.close();
-
}
-
-
byte[] serialized = baos.toByteArray();
-
-
//Deserialize to an instance
-
-
-
-
ReloadingObjectInputStream in =
-
new ReloadingObjectInputStream(cl, bais);
-
-
-
-
}
-
...
We could have used just a usual classloader (most common is URLClassLoader) and just point its classpath to the "/WEB-INF/classes". However there is also a trouble that the same classes are in the classpath of the web application classloader. The Java specification instructs classloaders to try loading classes with the parent classloader first, however in our case we would be able to reload those classes, so we need to invert this preference by delegating to parent only if we cannot find the class in the classpath:
JAVA:
-
...
-
private static class ReloadingClassloader
-
-
-
public ReloadingClassloader(
-
-
-
super(urls, parent);
-
}
-
-
public Class loadClass
(String name
)
-
-
//If already loaded just return
-
Class c = findLoadedClass(name);
-
if (c != null)
-
return c;
-
-
//First try own classpath
-
//then delegate to parent
-
try {
-
return findClass(name);
-
}
-
-
return super.loadClass(name);
-
}
-
}
-
}
-
...
We create the classloader by putting "/WEB-INF/classes" into the classpath. getEnvironment().getEntry() is just a way in Aranea to look up services, in this case we need a ServletContext:
JAVA:
-
...
-
-
-
//Get the ServletContext
-
ServletContext sctx =
-
(ServletContext) getEnvironment().getEntry(
-
ServletContext.class);
-
//Return a classloader for "/WEB-INF/classes"
-
return new ReloadingClassloader(
-
new URL[] {sctx.
getResource("/WEB-INF/classes")},
-
getClass().getClassLoader());
-
}
-
...
Now we have most of the machinery in place and before we move on to Aranea filter implementation we can spare a thought on the limitations of the solution. Obviously it will only work on serializable classes. What's more, to get actual gain from it all classes should have serialVersionUID set to some fixed number (e.g. "0"). This should be done so that small changes in the method and class signatures wouldn't cause a deserialization failure. Even then removing a class (even an inner or anonymous class) will cause serialization to fail as well as changing the field types or order. However it is still much more than HotSwap would allow (at least at the moment).
Now we can go on with implementing the Aranea filter that will do the work. There are several reasons why this solutions suits Aranea so well:
- Aranea is originally assembled from independent components by containment. Therefore one filter can define the classloader to load its children and do the rest of the tricks.
- All application widgets in Aranea are serializable and loaded by the parent classloader (most of the filters are configured by Spring and loaded by its classloader).
- Aranea components can only access their parents through the environment, which can be taken away at any given moment. Moreover parents have references only to their direct children, and can communicate with the indirect children only through messages.
Thus we can create a filter and put it in appropriate place just above the application widgets and we will be able to seamlessly reload all application widgets code.
At the moment we will assume that creating a new classloader and serializing/deserializing does not visibly affect response time in development, so we will just reload all classes before every request. Thus we do not need to check exactly which classes have changed, since any possible changes will be reloaded.
Let's start by creating the filter service itself. It will have to take the child class name as a string, since we will need to load it reflectively in a freshly created classloader.
JAVA:
-
public class StandardClassReloadingFilterWidget
-
extends BaseApplicationWidget {
-
-
private String childClassName;
-
private RelocatableWidget child;
-
-
public void setChildClass
(String childClass
) {
-
this.childClassName = childClass;
-
}
-
-
-
//Create the classloader and use it
-
//to load the child class
-
-
Class childClass = cl.loadClass(childClassName);
-
-
//Create an instance of child class and
-
//attach it to the filter
-
child =
-
new RelocatableDecorator(
-
(Widget) childClass.newInstance());
-
addWidget("c", child)'
-
}
-
...
Here we create the child widget reflectively in the classloader and add it under a name "c". The only interesting part is that we also wrap it into a RelocatableDecorator, which we will return to later. Now the next method is called before every request and will do the actual reloading:
JAVA:
-
...
-
protected void update(InputData input)
-
-
try {
-
//Remove all references to parents and
-
//reload the child classes
-
child._getRelocatable().overrideEnvironment(null);
-
child = (RelocatableWidget) reload(child);
-
}
-
-
log.error("Failed to reload widget classes", e);
-
}
-
finally {
-
//Restore the references to parents
-
child._getRelocatable()
-
.overrideEnvironment(getEnvironment());
-
}
-
-
//Reattach the child new instance to the filter
-
_getComposite().attach("c", child);
-
}
-
...
Here the point of wrapping the child into the RelocatableDecorator comes out -- as all references from children to parents in Aranea have to go through the environment, by removing the environment from the child we also remove all possible links up allowing to serialize children only.
Now the filter is complete (except for the trivial render() method which we leave as an exercise for the reader) and can be tested in e.g. main example or any other Aranea application by adding a similar configuration entry:
XML:
-
<bean id="araneaApplicationStart" singleton="false"
-
class="org.araneaframework.framework.filter.StandardClassReloadingFilterWidget">
-
<property name="childClass" value="org.araneaframework.example.main.web.DevelWidget"/>
-
</bean>
Instead of the usual:
XML:
-
<bean id="araneaApplicationStart"
-
class="org.araneaframework.example.main.web.LoginWidget"
-
singleton="false"/>
You will most probably want to define a different root widget class (in this case DevelWidget), which will start the usual LoginWidget in a StandardFlowContainerWidget. Otherwise you will lose the reloading filter on login.
So, on the plus side we managed to build a useful feature that will save countless developer hours by adding just one filter in a correct place. We also worked through a nice enough example of how to add framework features to Aranea (there definitely are a couple of gotchas here, but they are few and described in the reference manual). The filter can be used to reload widgets and all their helper classes, which makes up basically all of the Aranea application web layer. Since JSPs and most other templating engines can also reload their code easily we get to see all of the changes to the web almost instantly.
On the minus side we are reloading all of the classes in the web layer and serializing/deserializing all of the state on every request, which may become costly for a large application. We could somewhat remedy it by checking that classes have changed before reloading them. Also this trick won't work for the business layer, since it is usually not serializable and can be referenced from many different places. There are some possible ways around it by they need much more effort and deserve a separate post in the future 