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-10LISTING 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 AM