J2EE Class Loader Special
I was stumped for a while when working on a project for work.
I must underline that, prior to developing this app, I’ve never really ventured into the J2EE world. I knew it was a bit different from J2SE, but I didn’t know what the differences were.
I’ve learned a couple of things about @Singleton, @Startup, @DependsOn, and other annotations. I like Java annotations in general, so using them felt right from the get go. Dependecy injection is an awesome thing. So I went on to create my class that would initialize some components and load some resources.
@Singleton @Startup
public class MySingletonEngine {
private MyDirectory directory;
@PostConstruct
private void postConstruct(){
directory = new MyDirectory(); // this works fine
MyDirectoryListener listener = new MyDirectoryListener(); // this fails
directory.addListener( listener );
}
}
Both MyDirectory and MyDirectoryListener were provided in separate external libraries.
At first, I thought that one of the libraries does not get included in the classpath, for whatever reason. So I removed it, noticed that Maven build fails, added it again, and, obviously, the Maven build succeeded. But the problem persisted…
A colleague of mine pointed me towards inspecting the classpath at runtime.
So I did what I’ve known from J2SE:
String classpath = System.getProperty("java.class.path");
String[] paths = classpath.split(File.pathSeparator);
for( String s : paths ){
System.err.println( s );
}
And the program printed out two entries:
[err] /Users/jakub/Developer/projects/servers/was/bin/tools/ws-server.jar
[err] /Users/jakub/Developer/projects/servers/was/bin/tools/ws-javaagent.jar
Clearly not what I’d expected. It didn’t have any of my projects or Jars.
This reminded me that the way J2EE containers separate applications, is through class loaders.
So, happily, I changed my code to show entries from the class loader:
System.err.println("Getting resources from the system class loader.");
Enumeration resources = ClassLoader.getSystemClassLoader().getResources("");
while( resources.hasMoreElements() ){
System.err.println( resources.nextElement() );
}
No dice. Same results.
This didn’t make sense to me, as the very class that I was in was not on the classpath!
So I’ve decided to take the classloader of the class that I was in (which was clearly successfully loaded):
System.err.println("Getting resources from some class loader.");
Enumeration resources = this.getClass().getClassLoader().getResources("");
while( resources.hasMoreElements() ){
System.err.println( resources.nextElement() );
}
That worked much better! All my Jar files and projects, just as I’d expected.
Trying to load the class manually seemed to work using that class loader, too:
this.getClass().getClassLoader().loadClass("xyz.jelonek.MyDirectoryListener"); // works fine
ClassLoader.getSystemClassLoader().loadClass(“xyz.jelonek.MyDirectoryListener"); // ClassNotFoundException
Then I noticed that there was a difference between MyDirectory and MyDirectoryListener; the former was used in an instance variable declaration:
private MyDirectory directory;
while the other was not.
As soon as I added it to the class definition:
@Singleton @Startup
public class MySingletonEngine {
private MyDirectory directory;
private MyDirectoryListener directoryListener;
@PostConstruct
private void postConstruct(){
directory = new MyDirectory(); // this works fine
directoryListener = new MyDirectoryListener(); // this works fine now!
directory.addListener( directoryListener );
}
}
it started to behave exactly as I would’ve expected from the very beginning.
I still don’t know why is this happening, exactly (and I do intend to find out), but I did manage to find a solution:
After an Enterprise Java Bean is constructed (@PostConstruct is called), the default class loader is still the container’s class loader, not my WAR’s.
For now I believe this is a bug, and I’ll report it to WebSphere.
But it could be that it’s the most obvious thing in the world to J2EE veterans, but for me, this was something new…