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?
Yep, JBoss’ classloader is pretty much the only one of all the appservers I’ve tried that has genuinely surprised me. It works in its own bizarre unique way and seems to gleefully ignore everything any spec has ever said about classloading.
What’s even more fun is how easy it is to clobber jboss internals with your own apps (eg, log4j).
JBoss is not the only popular app server that doe sthis..
Hwoever you will be gald to know that JBOss gets anew classloader that works the way you expect..
In the 3.0/3.2 versions, JBoss’s classloading implementation was a completely buggy piece of crap until about the fall of 2002, when they fixed a number of horrific bugs (for example, using plain java objects from a jar that also had EJBs would cause those EJBs to deploy, etc.).
From about Nov. 2002 until a month ago, I got by pretty well with an EAR setup relying on the unified classloader, which consisted of EJBs, WARs, and plain code in JARs, all listed in the application.xml, and with code being pulled in by one jar which had a manifest class-path entry referring to other code. Then all of a sudden, I crossed some magic threshold, and all hell broke loose. Multiple instances of the same class object would start showing up, resulting in classpath exceptions, when traversing EJB relations. Seemingly no way to resolve it, despite trying to change the order of various jars or similar tactivs. Then with a bit more unrelated code added to the ear, all of a sudden on each startup a randome EJB or two would be listed as illegal (with no code changed anywhere). Playing around with the jar ordering some more in a random fashion resolved that, but I still have the issue with multiple class copies. The only way I have been able to resolve that is to stop using Entity ejbs entirely, which I was halfway in the process of doing anyways. Good riddance.
In short, classloading in JBoss is still royally fucked.
Actually, you can get each of your ears to load separately by setting a loader-repository. See the XML fragment in one of my UCL rants: http://members.capmac.org/~orb/blog.cgi/tech/java/jboss_ucl.html
This isn’t a perfect solution, but it will keep your classes/resources from being visible outside of the ear and solve the immediate problem.
1