JBoss documentation is quite poorly done. Firstly, the official documentation is only available on purchase. The bigger problem is that most developers will still be as clueless on how best to package their J2EE application for use in JBoss even after purchasing and reading through the official documentation. The web is filled with horror stories from developers who couldn't figure a way out of classloader troubles. Most developers cop out by adding all jars they are dependant on to their server's global lib. But this clearly violates scoping considerations and increases the risk of conflicts between unrelated applications deployed in a single container. E.g., if two applications require different versions of the same jar, this approach would require the use of two different jboss servers.
The current document is an effort to capture what I figured out the hard way on JBoss 3.2.1 so that -
One needs to understand some of the basics of classloading in order to fully understand the approach I am presenting here. Let's get past them upfront.
A good way of digesting these principles is to look at Tomcat's classloader HOWTO.
Anyone worth calling oneself a java developer would know what a JAR file is. It's an archive, just like ZIPs and TARs are. In addition to being a simple archive, a JAR file can store some very useful meta information about its contents. A special file, META-INF/MANIFEST.MF is where a jar records meta information.
EJBs are packaged as jar files as well, albeit with one or more special files under META-INF, called deployment descriptors.
A WAR file packages a web application. What does a web application consist of? It consists of
The latter are stored under a special folder named WEB-INF. Both are then archived in JAR whose extension is modified as WAR to distinguish it as a java web application.
Similarly, RAR files are JAR files used to package resource adapter modules as defined under the J2EE Connector Architecture (JCA) and SAR files are the ones used to package JMX-enabled services in JBoss.
Just as all rivers are destined to go merge with the sea, all these archives that relate to a single enterprise application may be packaged under one EAR.
It's been a common practice among app servers to use a different class loader for loading each app archive - WARs, SARs and EJB JARs. What if all of them require a common set of utility classes? Well, those were loaded by a common ancestor of the app archive classloaders that looked in a global lib somewhere (e.g. JBOSS_HOME/server/jboss-instance-name/lib/) for the shared classes. (Or worse, bundle them redundantly as part of each app archive.)
But what if you despise the use of a global lib of shared classes, esp. in the case of a J2EE container used to host multiple enterprise apps, each coming from a different vendor? Ideally, we should be able to drop in a EAR file that's completely self-contained and it should not cause of any of the other applications deployed in the same container to break. The challenge is then to find a way to indicate to the app server that a WAR inside an EAR depends on another JAR that's inside the same EAR. This way, we can bundle all the utility JARs as well as part of the EAR.
The JAR file spec was enhanced in Java 1.2 to provide a way of specifying dependencies on other classfiles. Here's the relevant extract from Java 1.2 Extension Mechanism spec.
The manifest for an application or extension can specify
one or more relative URLs referring to the JAR files and
directories for the extensions (and other libraries) that
it needs. These relative URLs will be treated relative to
the code base that the containing application or extension
JAR file was loaded from.
An application (or, more generally, JAR file) specifies
the relative URLs of the extensions (and libraries) that
it needs via the manifest attribute Class-Path. This
attribute lists the URLs to search for implementations of
extensions (or other libraries) if they cannot be found as
extensions installed on the host Java virtual
machine. These relative URLs may include JAR files and
directories for any libraries or resources needed by the
application or extension. Relative URLs not ending with
'/' are assumed to refer to JAR files. For example,
Class-Path: servlet.jar infobus.jar acme/beans.jar images/
Multiple Class-Path headers may be specified, and are
combined sequentially.
We can thus refer to the utility JARS bundled with the EAR inside the manifest of each WAR, SAR or EJB JAR that requires them. The EAR can then be deployed as one stand-alone unit under JBOSS_HOME/server/jboss-instance-name/deploy/.
A dated article at O'Reilly's OnJava.com says
Support for the extension mechanism does not exist for EAR, WAR, or RAR applications as defined in the J2EE specification, since these applications are not directly loaded by a ClassLoader instance. Web applications still have to have libraries packaged in the WEB-INF\lib directory and resource-adapter applications can have libraries bundled directly in the RAR file.
This doesn't seem to be the case for atleast SARs in JBoss 3.2.1. I was misled by the above quote in my search for a solution to my packaging woes until I stumbled across a JUnit Test (hah! yet another use of JUnit - as a spec!) from JBoss that was checking for the correct functioning of Class-Path tags in manifests even in SARs packaged within EARs. The same seems to be the case for WARs - I'm not sure how I figured this out/tested it out but that's what my memory says. Older JBoss Manuals seem to confirm this.
There's one additional thing that you need to do in order to achieve our goal of a self-contained EAR that does not step on the toes of other application within the same container.
Probably with an eye over alleviating the start-up troubles that novice users have with all this classloader complexity, JBoss circumvents the hierarchical nature of classloaders with the use of what they call as "Loader Repositories". A set of classloaders (called UnifiedClassLoaders, UCLs, to distinguish them) gang up to form a repository of all the classes that they have loaded. Every class loader within the group checks with the repo to see if the class has already been loaded by itself or by a peer. If found, the class is used no matter who loaded it. If not, the class loader that's been asked to load the class consults its ancestors and its own classpath to find the class. If the class is still not found, other UCLs in the group are consulted. What does this mean? It allows us to treat the combined classpaths of all the UCLs in a group as one long classpath. What's the downside to it? If multiple versions of a class are found in the effectively combined classpath, only one of them would ever get used. The others are silently ignored.
By default, all the EARs in a jboss instance, under, JBOSS_HOME/server/jboss-instance-name/deploy/ use the same loader repository and hence, a class found in one can be used by the other. But we don't like calling this bug a feature as it goes against the idea of scoping by EAR. Fortunately, JBoss provides a way to supress this behaviour through what they call as deployment based scoping. Here's the relevant extract from JBoss documentation.
With deployment based scoping, each deployment creates its
own class loader repository in the form of a
HeirarchicalLoaderRepository3 that looks first to the
UnifiedClassLoader3 instances of the deployment units
included in the EAR before delegating to the
defaultUnifiedLoaderRepository3 . To enable an EAR
specific loader repository, you need to create a
META-INF/jboss-app.xml descriptor as shown in Listing 2-10
LISTING 2-10. An example jboss-app.xml descriptor for
enabled scoped class loading at the ear level.
<jboss-app>
<loader-repository>some.dot.com:loader=webtest.ear</loader-repository>
</jboss-app>
The value of the loader-repository element is the JMX
ObjectName to assign to the repository created for the
EAR. This must be unique and valid JMX ObjectName, but the
actual name is not important.
If you wish to deploy one application archive before the other for any reason, you can force JBoss to do so by changing URLComparator in JBOSS_HOME/server/jboss-instance-name/conf/jboss-service.xml to org.jboss.deployment.scanner.PrefixDeploymentSorter and adding a numeric prefix to each of the application archive file names. E.g., 1appA.ear will get loaded before 2appB.ear. All the archives that do not have a numeric prefix are loaded upfront before any that do, preserving the original order of loading except for the ones you prefixed.
I've deliberately avoided discussing the connection between hot swapping and class loaders to keep the discussion simple.
Posted by prasad at October 30, 2003 08:33 AMMy trouble with the 2 war's is fixed in jboss 3.2.5
Thanks for the link! I've tried that but JBoss complains that only top level deployments may have a loader repository... :(
And I don't need full separation, I need WAR (child) priority. (And EAR priority over other deployments, so I do need an ear scooped loader repository. At least I don't know any other way to separate my EAR from the others)
What I would like to have is a WAR level config with something like askParentFirst=true/false.
Seems like trouble...
Posted by: 101 at July 14, 2004 04:07 AMAttila (alias 101):
See http://sourceforge.net/docman/display_doc.php?docid=16524&group_id=22866.
You need to specify a custom loader repository in each WAR with
java2ParentDelegation to false in both.
cheers
prasad
Thanks for this nice doc!
And while we are at it... :)
Is there a way to separate the cloassloaders of two war's packaged togather into an ear?
I'm struggling with a problem, and if I could do this I could make a workaround work. How ironic...
I have an above mentioned loader repository specified for the ear. I've tried to specify the java2ParentDelegation=false parameter (so child classloaders are trying to load first, not their parents), tried changing the same for the JBoss Tomcat config, no luck...
Any help is appreciated!
yep, thanks for this nice doc! :)
Posted by: 101 at July 13, 2004 08:02 AMA very well written and nice article. Very lucid.
Posted by: Rahul at June 30, 2004 04:02 PMIf I have this in jboss
some.dot.com:loader=webtest.ear
Where do I then place my common jars? and how do i tell my application (.war's to reference them) ?
Posted by: tomer at April 27, 2004 05:33 AMI love to read when someone writes precisely and still covers it elegantly.
Posted by: Javed at April 20, 2004 06:16 PMVery interesting description. A lot of developers start working with J2EE using various IDEs which does everything for them and then they get used to it. I have seen a lot of horror stories happening because of packaging issues and then while deploying one gets the NoClassDefFound exceptions to no end. The most common one was with log4j which is typically used both in the web components and the ejbs.
Posted by: VaibhaV Sharma at February 20, 2004 02:26 PMNice document
Posted by: manilal at February 5, 2004 11:07 AM