Wednesday, January 28, 2009

Using Jar Class Loader (JCL) to reload classes from a JAR

I have been playing recently with JCL to load classes on demand from a jar file.

The immediate use of this is to build a plugin engine. A jar file might contain some code that can change without the need to restart the application using it.

Suppose you are told there is a specific functionality that should be changing regularly without affecting your server uptime. If you are behind a simple server container using WAR deployments you could use this method to ensure no WAR redeployment is needed when your functionality is in fact improved/changed.

I have put a running sample on SVN (JDK 5 needed, if you need to support a previous version just download jcl and compile it yourself).

To see it in action just run run.sh (using linux this time). If you are running Windows just get the *.sh files and provide similar *.bat files. Yes I know, Ant is cross platform and I should have used it for this post. Next time I will use Ant and even Maven ...

You will get full traces of what is happening every two minutes. Change constant 'version' in PluginImpl.java and run build-plugin.sh to see how the trace message is updated with the new version showing you can change your java class implementation at any time and your application does not need to be restarted.

I should remark here that when used from an application server like JBoss which uses its own class loader it is necessary to add the interface package to the newly created class loader (use jcl#add() for it). However if the plugin class refers directly to some classes in the current class loader jcl#add()-ing individual packages would be overkilling. You need then to use a custom loader like:
package mypackage;

import org.apache.log4j.Logger;

import xeus.jcl.loader.Loader;

class MyLoader extends Loader {
private Logger logger = Logger.getLogger(EISLoader.class);

public MyLoader() {
order = 4;
}

public Class load(String className, boolean resolveIt) {
Class result;
try {
result = this.getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
return null;
}

if (logger.isTraceEnabled())
logger.trace("Returning class " + className + " loaded with parent classloader");

return result;
}

}
Then later use that loader as in:
MyLoader loader=new MyLoader();
loader.setOrder(4);
jcl.addLoader(loader);

I also found problems with log4j since JBoss uses its own and it might be old you might get:
java.lang.NoSuchMethodError: org.apache.log4j.Logger.isTraceEnabled()
At least in the case of JBoss 4.0.4 I managed to solve the problem using log4j-1.2.15.jar renamed as $JBOSS_HOME/lib/log4j-boot.jar and $JBOSS_HOME/server/$configuration-dir/lib/log4j.jar.

We can also go by ourselves and try to get this done without any special library using UrlClassLoader however I have done some tests and this solution appears to work in Windows but not in Linux. Once the plugin class is loaded the first time any further replacement will not be noticed in my Ubuntu Intrepid 2.6.27-9-generic Kernel.

3 comments:

coldserenity said...

Thanks for the post.

Remark. In the code:
Class name = MyLoader
Constructor name = EISLoader

ayuSmAn said...

Finally I found an article on JCL.

I am trying to use JCL in an web application where JCL will load certain classes dynamically from jars, but I want to use the classes from within an EJB/jsp (My application is an EAR)

Can I do that? Right now the problem is the dynamically loaded class and the classes of the EAR are loaded in separate class loaders and can not access one another.

Nestor Urquiza said...

@ayuSmAn
Try it. IMO it should work. It's being a while since I played with all this. Hopefully OSGI will terminate this classloader problems we face every other day.

I think the important thing is just to include the necessary classes in the custom class loader.

It was definitely fun this project is all I can say.

Cheers,

-Nestor

Followers