There are so many tricky stories around on classloaders in Java. Classloading is a powerful technique from the one hand. From the other it’s the place where it so easy to make mistakes. Some are simply afraid using them.
I solved a simple puzzle with isolating classes I want to share.
There are integration tests in a project. Those integration tests starts several web applications, bind them together and to some black-box tests. The vital part of such tests is to provide a classpath isolation between test classes and classes of applications that are running.
To start each of those web servers I use Jetty Embedded. Well, I load Jetty classes for each web application I start within a separate classloaders hierarchy. I like Jetty, but I want to isolate any side effects, thus loading it several times, thanks it’s tiny, is the way to go.
All helper classes to start/stop/configure web applications forms an API that helps for a tests run.
It turns out to be tricky to isolate those runner classes from a test classpath. From one hand we’d like to have an APIs available for test class to use, from the other hand, it is still required to make sure the classpath of web application, hence Jetty Embedded it not polluted with test module dependencies.
Say we have and
IMPL.jar. We are looking on how to load the
IMPL.jar with a clean
classpath and still have a change to bind it to the
API.jar that is a part of some other huge classpath.
The goal is to the following:
- Test framework uses
- IMPL.jar also uses
API.jar, but it does not see any test-related classes
The problem is that it is test framework / test runner that loading test classes. We are unlikely to change that. Otherwise we have to take care of test runners, IDEs, CI, debugging.
Load all classes within one JVM. This violates the task, but may be the easiest way. The future issue here can be a jar hell, if one has different libraries used in the application / tests or the loader.
Another solution could be in using OSGi. It’s a complicated framework that uses security manager to isolate classes visibility.
Future solution with Java 9 modules is also a way to go. But for now, it’s too early.
The most simple solution is to load classes with child-first classloader. This is NOT the way to isolate dependencies, but at least this is the way to have IMPL.jar dependencies win on classloading.
There are at least two things to take case of
Case 1 : JVM classes
Child first classloaders should still load JVM classes first.
It may turn out the classpath contains some classes that are now included into JVM. Those classes are still included into a package to provide a compatibility with older JVM versions. Those classes are simply ignored by the JVM when default classloading is used.
It may generate tricky issues when child first classloader attempts to load wrong classes since those classes will likely register themselves in the OS.
The solution here is the following. We crete a classloader:
This classloader is the first one to check in the child classloader implementation. It is the easiest way I know to delegate to the system classloader. The Classloader itself uses native method in order to delegate to it.
Case 2: Resources
It’s so easy to implement child first classloader. It is also so easy to forget about resources. The child first strategy must be implemented for resources too. Otherwise, it may break some libraries which uses resources.
Finally classloading with child first delegation does not solve the initial problem. It does not allow to
fully isolate test classes (e.g. test framework) from
The Proxy Trick
The idea is to load
API.jar twice and than use to
java.lang.reflect.Proxy to bind interfaces part from
tests classspath to implementations.
A trick is to have yet another classloader with
null as parent. The loader classpath includes
IMPL.jar and dependencies of
Next we create a
java.lang.reflect.Proxy to create an instance of an API interface that delegates
to the implementation class that is loaded by the other classloader. We are not able to cast here,
since we have
API.jar loaded twice.
Unfortunately, this will only work when API interface methods uses JVM classes as parameters
and return types. There are no common
IMPL.jar and tests, thus ClassCastException will be thrown for other types.
Recursive Proxy Trick
The last limitation can be solved by a bit more complicated trick of 3 steps
- Create a proxy for API interface
- In the implementation, check all parameter types and proxy every parameter with non system classloader
- In the implementation, proxy return values in opposite way
- Apply the following proxies recursively if
API.jarinterfaces are complex
This approach allows to use JVM classes and
API.jar interfaces between the bound. It will not work
with classes, since it is the limitation of
The overall trick is implemented as follows
Current implementation works only with API interfaces. It does not allow sharing classes or enums.
API.jar can be easily supported be adding an explicit checking.
Classes cannot be mapped this way. At least because it is not allowed in
From the other hand, it’s not clear how class state can be transferred at all.
It may be necessary to change
Thread#contextClassloader to the right one before calling a
method from different classloader. This trick makes dynamic classloading in libraries work
correctly for some cases. Otherwise, there is a possibility that a class from another classpath
is created from
IMPL.jar code (say, via
I used the approach to implement integration tests that are running several web application instances within one JVM. All instances are now isolated from each other and from tests classpath.
Running everything within one JVM helps to avoid issues with leaked processes or applications as well as allows one to debug every application or even all applications easily!comments powered by Disqus