Java

JBoss Classloader woes, rants and redemption

The redemption part is that I finally got the JBoss classloader to let all of my applications play nicely together. The ugly part is how I got there – and it gives some unusual insight into the JBoss classloader.

Here’s the situation – EAR1 is Liferay Portal, which requires that Hibernate JAR files are in the JBoss instance’s classpath. EAR2 is a set of internal applications that use a different version of Hibernate, and is configured to load in its own classloader. When EAR1 is not deployed, all libraries and components of EAR2 deploy and execute correctly. When EAR1 IS deployed, Liferay behaves fine, but the internal apps break – the log reflects that some of the configuration for EAR2 is being loaded from EAR1, and a ClassCastException ensues.

The code I was using to load Hibernate in EAR2 is as follows:

private void configure() throws HibernateException, IOException {
Configuration conf = new Configuration();
configure(conf);
}

private void configure( Configuration config ) throws HibernateException {
_sessions = config
.addClass(Inventory.class)
.addClass(InventoryLocation.class)
.addClass(SalesObject.class)
.addClass(MerchandiseItem.class)
.addClass(SKU.class)
.buildSessionFactory();
}

In this situation, the default behavior is that Hibernate simply finds hibernate.properties in the classpath. And while the initial configuration showed that it was loading the correct properties and even the correct version of Hibernate, but the final configuration showed that it was using config from the other EAR!

Strangely, using the following code fixed the issue:

private void configure() throws HibernateException, IOException {
Configuration conf = new Configuration();
Properties props = new Properties();
props.load(this.getClass().getResourceAsStream(“/hibernate.properties”));
conf.setProperties(props);
configure(conf);
}

Now it seems logical that net.sf.hibernate.cfg.Configuration probably executes the same code to load the properties, and it’s obvious that I was loading the correct Hibernate JAR based on the version in the log, however, it seems that somewhere along the way, something got delegated to the system classloader FIRST, loading the hibernate.properties from EAR2. By moving the configuration to my class, it appears I forced JBoss to use the scoped classloader I named for EAR2, thus loading the correct properties.

Once again, I have a massive beef with this. I can’t really see any instance where I want a resource loaded in one EAR to be accessible in another EAR. hibernate.properties should NEVER end up in the system classpath when it’s deployed in an EAR. But this seems to be the default behavior of the JBoss Universal ClassLoader.

From the documentation: “If you need to deploy multiple versions of an application the default 3.x class loading model would require that each application be deployed in a separate JBoss server.”
Also: “All UnifiedClassLoader3s. The All UnifiedClassLoader3s node represents the UCLs created by deployers. This covers EARs, jars, WARs, SARs and directories seen by the deployment scanner as well as jars referenced by their manifests and any nested deployment units they may contain. This is a flat namespace and there should not be multiple instances of a class in different deployment jars. If there are, only the first loaded will be used and the results may not be as expected.”

NOT AS EXPECTED? Well duh! If a 3rd party app loads a library, why the blazes would I expect their version of a library to replace mine, or vice versa? J2EE has robust, hierarchal structures in which we can deploy applications. What’s the point of all that if that stucture’s annihilated by loading everything into a flat ClassLoader?

Advertisements
Java

The JBoss ClassLoader is pain incarnate

Okay, not really incarnate, since it’s not alive. And it might not really be about JBoss – could be a general J2EE problem, though I don’t have time to look at the spec.

The problem stems from the seemingly innocuous desire to run multiple enterprise applications in a single instance of JBoss/Tomcat. Seems innocent enough, doesn’t it? In the red corner, we have Liferay Enterprise Portal, ready to be deployed as it’s own pre-packaged EAR file. In the blue corner, we have several internal web apps bundled in an EAR that we’d really like to run in the same JBoss instance. Strangely, when I’d run our internal apps, it was trying to use Liferay’s version of Hibernate. How odd.

So what’s the problem? Well, it seems that the default behavior in JBoss is that everyone gets to share their classes – the first class loaded wins. WHY?!? If I have 2 different EAR files, with different versions of libraries, why in the world would I want to use ANY other version of that library than THE ONE I PACKAGED WITH MY APPLICATION! There is, by the way, a proprietary way to tell JBoss NOT to do this. To specify a fresh classloader, isolated from your other apps (per EAR, at least), add this to your jboss-app.xml:


<!DOCTYPE jboss-app
PUBLIC "-//JBoss//DTD J2EE Application 1.3V2//EN"
"http://www.jboss.org/j2ee/dtd/jboss-app_3_2.dtd">
<jboss-app>
<loader-repository>acme.com:loader=acme.ear</loader-repository>
</jboss-app>

That wasn’t so bad now, was it? But wait, this is but the beginning of your troubles. It seems that when you switch into this mode, the classloader suddenly becomes VERY picky. Things you were able to do before suddenly become VERY difficult. Parts of your application that used to Just Work suddenly stop working. And of course, classloader issues rarely actually seem to show up as a ClassNotFoundException. My favorites are always the InvocationTargetException and the NPE, though there are many more obtuse messages than that.

It turns out that you were probably depending on JBoss’ vaunted Universal Class Loader more than you ever realized. So in some ways, it did make your life easier. Now, your application throws a temper tantrum. If you have a log4j JAR in your application, JBoss won’t like that you loaded that instead of its version of log4J. That jar you had in a few of your web apps? Well, now it may throw a tantrum as it tries to use the version you loaded in WebAppA in WebAppB. I don’t think it’s actually more strict, rather it seems that there are many things that just work under the standard configuration that we never worried about before.

Now it’s entirely possible that I don’t know what I really want, and that I’m dead wrong, or it would kill performance. But what seems to MAKE SENSE is a full, hierarchal class loading inheritance system. If MyApp1.war exists in MyApps.ear, and MyApps.ear has a version of struts.jar, then MyApp2.war can use that version, but MyApp1.war can supercede that with its version. As things are, now that I can partition this EAR, I’m wading through all the changes it requires, and it STILL won’t fix everything. While I’ll at least be able to run someone else’s app and my app in the same instance, what happens when I get a second third-party application in there? Now, without tweaking, it and Liferay will try to load in the same classloader. Seems like an unnecessary and counter-intuitive mess.