Monday, June 11, 2007

Java ClassLoader trick

I work on the Glassbox Java Troubleshooting Agent, and it uses Ant scripts to automate installation into various Java containers. The version of Ant we use was conflicting with existing libraries (in CruiseControl and JBoss), so I needed to create a ClassLoader sandbox.

The OverridingURLClassLoader.java does the trick. This is just a URLClassLoader with one additional argument: the name of the class to redefine. This ClassLoader will enable one of your application classes to be re-defined in a ClassLoader sandbox and avoid Jar hell when deploying into any random environment.

That one application class could be defined by both the application ClassLoader and this new ClassLoader, and those two classes won't be compatible or assignable. An interface or base class could be shared by both though (and only defined by the application ClassLoader). The AntInstaller.java is using an interface to type the returned newInstance() object.

Here is the constructor that takes the name of a single class that should be re-defined by this ClassLoader (instead of the parent ClassLoader):

class OverridingURLClassLoader extends URLClassLoader {

String redefineClassName;

public OverridingURLClassLoader(String redefineClassName, URL[] urls, ClassLoader parentLoader) {
super(urls, parentLoader);
this.redefineClassName = redefineClassName;
}


The loadClass method checks cached classes, then classes defined in the list of URLs. If that fails, then before the parent is called a check for
if (name.equals(this.redefineClassName)) is done. If that class is being asked for, then it is re-defined using this instance of a ClassLoader:

public Class loadClass(String name) throws ClassNotFoundException {
Class c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);

} catch (ClassNotFoundException e) {
if (name.equals(this.redefineClassName)) {
String path = name.replace('.', '/').concat(".class");
URL resource = getParent().getResource(path);
try {
byte[] bytes = toBytes(resource.openStream());
c = defineClass(name, bytes, 0, bytes.length);
} catch (IOException e1) {
throw new IllegalStateException("Can't get class definition", e1);
}
} else {
c = getParent().loadClass(name);
}

return c;
}
}

return c;
}

No comments: