October 30, 2003

Packaging J2EE applications for JBOSS 3.2.1

Motivation

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 -

  1. More experienced users of JBoss can tell me if I found the best possible strategy or not. I usually stay clear of J2EE hype, so I'm not 100% conversant with the nooks and corners of this land. I do however understand the intricacies of class loaders in Java and the approach I came up with is based on that understanding. All criticism of this approach is welcome and actively solicited.
  2. Others can take advantage of my findings and avoid rediscovering the wheel the hard way.

Relevant Classloader Basics

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.

  1. Two java.lang.Class instances loaded by different classloaders are not considered equal!
  2. When you instantiate a class the first time, the classloader used is the one that loaded the instantiating class.
  3. Classloaders can take part in hierarchies. A child classloader can make use of the classes already loaded by its ancestors but not vice-versa.

A good way of digesting these principles is to look at Tomcat's classloader HOWTO.

Intro to JARs, WARs, SARs, RARs and EARs

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

  1. HTML/JSP pages and other resources such as images/javascript files/stylesheets that need to be reachable via URLs.
  2. Additional files such as java servlets, utility classes and jars for use in servlets/JSPs and deployment descriptors that should not be reachable via URLs.

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.

The problem of dependancies

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 solution: Bundled Extensions introduced in JAR 1.2

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/.

Aside

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.

JBoss's classloader repo hack

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.

Other related Issues

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
Comments

My trouble with the 2 war's is fixed in jboss 3.2.5

Posted by: 101 at July 14, 2004 09:47 AM

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 AM

Attila (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

Posted by: prasad at July 13, 2004 02:15 PM

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!

Posted by: 101 at July 13, 2004 11:09 AM

yep, thanks for this nice doc! :)

Posted by: 101 at July 13, 2004 08:02 AM

A very well written and nice article. Very lucid.

Posted by: Rahul at June 30, 2004 04:02 PM

If 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 AM

I love to read when someone writes precisely and still covers it elegantly.

Posted by: Javed at April 20, 2004 06:16 PM

Very 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 PM

Nice document

Posted by: manilal at February 5, 2004 11:07 AM