Let's begin by looking at how to package J2EE apps.
The two most commonly used deployment units inJ2EE apps are Web ARchives (WARs) and EJB JAR files. These are JAR-format files that contain:
The implementation classes, binary dependencies, document content (such as JSP pages, static HTML and images) and deployment descriptors of a web app. If we don't use EJB, a WAR can contain all code and binaries required by a J2EE web app.
Typically, each of these deployment units will include both standard J2EE and proprietary, app server-specific deployment descriptors. WAR and EJB JAR deployment units comprising an app using the entire J2EE stack can be included in a single J2EE deployment unit, called an Enterprise Archive (EAR). EAR files contain an additional deployment descriptor, app.xml, which identifies theJ2EE modules composing the app. In this tutorial we've considered the WAR and EJB JAR file module types, which are used most often in practice. It is also possible to include Java app clients and J2EE Connector Architecture (JCA) Resource Adapters in an EAR. Where collocated apps are concerned, EAR deployment is usually the best option, providing convenient deployment and accurately reflecting the semantics of the app. Hence we'll use it for the sample app. However, EAR deployment may be less appropriate for distributed apps. It's pointless to adopt a distributed architecture if web-tier components on each server always use EJBs on the same server. Thus in a distributed app, the EJB client components (WARs) in an EAR are likely to communicate at runtime with EJBs running on another server, rather than the EJB instances on the same server. One of the key arguments in favor of adopting a distributed architecture is the potential to devote additional hardware to known bottlenecks -for example, EJBs that perform time-consuming processing. In such cases, EAR deployment of all components on all servers may be wasteful and misleading, as the aim is not for all servers to run all app components. The alternative to EAR deployment for distributed apps is to separate web apps from EJB deployments, as we know that EJBs will be invoked via RMI. However, deployment in separate modules may be more complex. We will need to include the EJB client views (home and component interfaces, but not EJB implementation classes and any helper classes they use) in both EJB and web deployments.
As we noted in , it's possible to run the J2EE Reference Implementation's Verifier tool against an EAR, WAR, or EJB JAR deployment unit, to check compliance to theJ2EE specifications. The sample app's Ant build script includes a target to run the verifier. Such verification should be performed regularly, to ensure that apps are specification-compliant and because it provides an easy pre-deployment check for errors. The verifier tool reports problems such as missing classes or invalid deployment descriptors in a detailed and consistent manner. This may provide clearer information on the cause of a deployment failure than the output of someJ2EE servers.
Most servers allow deployment units to be deployed in "expanded" or "exploded" form: that is, as a directory structure, rather than an archive in a fixed directory structure. Expanded deployment is typically most useful in development; we will want to roll out single deployment units into production.
The advantages of expanded deployment in development are that it often enables individual files to be updated without full redeployment. For example, it's unacceptable to have to redeploy an entire app to modify a JSP during development. The sample app's Ant build script includes a target to deploy the app as an EAR containing an EJB JAR file, but an expanded WAR. This makes it possible to modify JSP pages and other web content, without redeploying the app.
Perhaps the toughest issue in packagingJ2EE apps relates to class loading in apps consisting of multiple modules, this affects:
How we package apps, and especially where we include classes used by both EJB JAR and WAR modules.
Important |
Unless we understand how J2EE server class loading is likely to work and draw the appropriate lessons (we can only say "likely" as it differs between app servers), we risk encountering mysterious ClassNotFound or ClassCastExceptions. |
While there are good reasons for class loading to work the way it does, unfortunately the complexity of J2EE class loading can impact app developers and reduce productivity. Thus it is important to understand the issues involved, complex though they are.
Note |
The following discussion concentrates on packaging apps in an EAR (the commonest approach in practice), rather than packaging EJB JAR and WAR modules separately. |
Let's first look at how Java 2 class loading works. The following two basic principles will always apply:
Each class retains an association with the class loader that loaded it. The getClassLoader() method of Java.lang. class returns the class loader that loaded the class, which cannot be changed after the class is loaded. This is the class loader that will be used if the class attempts to load classes by name.
The documentation of the java.lang. ClassLoader class further defines the following behavior for class loaders:
Class loaders are hierarchical. When a class loader is asked to load a class, it first asks its parent class loader to try to load the class. Only if the parent (and parent's ancestors) cannot load the class, will the original classloader attempt to load the class. The top of the class loader hierarchy is the bootstrap loader built into the JVM, which loads java.lang. object().
As it's possible to implement a custom class loader (and most app servers provide several), it is possible to depart from the hierarchical behavior described in the last two bullets.
J2EE servers use multiple class loaders, largely because this allows dynamic app reloading. Clearly we don't want to reload all the app server's own classes on redeploying an app. This would mean that the app server would always need to be restarted. So app servers use different class loaders for app code to those they use for their own standard libraries, for example. Typically one or more new class loaders will be created for each app deployed on a server. However, multiple class loaders are not usually used for different app-specific classes in the same app unless we use EJB (JSP pages may be given a separate class loader, but this doesn't usually affect app code). As I've previously mentioned, using EJB considerably complicates the deployment model, compared to that for a pure web app. This is also true of class loading. In a WAR, we can simply include all binary dependencies in the /WEB-INF/lib directory. However, things get more complicated when WARs access EJBs. To see why, let's consider a common approach to implementing class loading in app servers. In an app deployed as an integrated enterprise app in an EAR, the EJB class loader is often the parent of the WAR class loader. Orion and WebLogic, for example, both use this approach. This is a natural implementation approach, as WARs will typically access EJBs (and therefore need to be able to see at least EJB client classes), while EJBs do not access web components.
Important |
However, it's not the only valid implementation approach, so the following discussion doesn't apply to all app servers. |
The resulting class loader hierarchy will look as shown in the following diagram. Actually more class loaders may be involved, but these are the three class loaders most significant to app code. In this diagram the class loader hierarchy is represented by enclosing boxes. The parent-child relationship is represented by an enclosing box:
Assuming standard J2SE hierachical class loading behavior, such a hierarchy will mean that any class can access classes in boxes that enclose its class loader. However, classes associated with the outer boxes cannot load classes in the inner boxes. Thus web app classes can see classes deployed in the app's EJBs and system classes. However, EJB classes cannot see web app classes, and classes installed at server-wide level cannot see any app-specific classes.
Surely this is all of interest only to implementers ofJ2EE app servers? Unfortunately, app developers can't afford to ignore, or be ignorant of, the implications of J2EE class loading. Assuming the class loader hierarchy described above, let's consider a plausible scenario. Imagine that an app class or a framework class used in both EJB and web components attempts to load a class within a WAR. Imagine, for example that a BeanFactory implementation used in the EJB container is also used by code within a WAR to load classes by name and manipulate them. Even though this infrastructure class is visible in the WAR, it cannot load WAR classes. Since it was loaded by the EJB class loader it cannot see classes in the WAR, which were loaded by a descendant class loader. We can't always solve this problem simply by holding class definitions in both WEB and EJB JAR file, because this may cause class cast exceptions, if the two class loaders,end up independently loading one or more classes. Thus there are two basic problems relating to J2EE class loading, in the common case where EJB and web modules are included in the same enterprise app:
Where do we hold the definitions of classes used in both EJBs and web apps?
Not only do implementations of class loading differ, but different J2EE specifications differ regarding class loading. The Servlet 2.3 specification (9.7.2) states that "It is recommended also that the app class loader be implemented so that classes and resources packaged within the WAR are loaded in preference to classes and resources residing in container-wide library JARs". This clearly conflicts with the standard J2SE class loading behavior, as described in the Javadoc for the java.lang.classLoader class. As the WAR class loader must be a dynamic class loader, it must be the child of another class loader provided by the app server. Hence the Servlet 2.3 recommendation is the opposite of normal Java 2 class loading behavior, which clearly states that classes will be loaded from the child class loader (in this case the WAR class loader) only if they cannot be resolved by the ancestor class loaders. This recommendation (note that it is not a requirement) is also unclear on where EJBs fit into the proposed class loading model. EJBs are presumably not considered to be "classes and resources residing in containerwide library JARs"*, in which case the requirement does not apply to them. The contradiction between the Servlet 2.3 and normal Java 2 class loading behavior is underlined by the fact that Sun's J2EE 1.3 Compatibility Test Suite fails on servers that default to implementing Servlet 2.3-style inverted class loading. For this reason, many servers either don't implement the Servlet 2.3 recommendation, or offer it only as a configuration option. The JBoss/Jetty bundle used in the sample app defaults to using normal Java 2 class loading behavior, although it can be configured to use Servlet 2.3 WAR-first behavior. Oracle iAS takes the same approach.
The main merit of Servlet 2.3-style class loading is that it can allow us to ship any patched libraries an app requires as part of the app, without altering the server installation. For example, the XMLC 2.1 web content generation technology discussed in requires patched versions of XML libraries shipped with some app servers. With Servlet 2.3 class loading, we can include the necessary patches in the /WEB-INF/lib directory, without any need to modify overall server configuration or any risk of conflict with other apps.
We also need to take into account further J2SE class loading refinements. Changes inJ2SE 1.3 make it possible for JAR files to specify dependencies on other JAR files, by specifying a space-separated list of relative file paths in a Class-Path header in their /META-INF/MANIFEST.MF file. Section 8.1.1.2 of the J2EE 1.3 specification requires that app servers support this for EJB JAR files. The following example from the sample app's ticket-ejb.jar file's MANIFEST.MF file illustrates the use of this mechanism in the sample app:
Class-Path: log4j-1.2.jar i21-core.jar i21-ejbimpl.jar i21-jdbc.jar
This declares that the app-specific classes in the ticket-ejb.jar file depend on four infrastructure JARs, meaning that the EJBJAR file doesn't need to include any third party classes. These paths are relative. All these JAR files are included with the EJBJAR file in the root directly of the app EAR, as the following listing of the EAR's contents shows:
META-INF/ META-INF/MANIFEST.MF META-INF/app.xml i21-core.jar i21-ejbimpl.jar i21-jdbc.jar ticket-ejb.jar ticket.war log4j-1.2.jar
Some app servers support the manifest classpath mechanism for WAR and EAR deployment units, but as these are not loaded directly by class loaders this is not required byJ2SE 1.3 or theJ2EE 1.3 specification. For example, see documentation at http://otn.oracle.com/tech/java/oc4j/htdocs/how-to-servlet-warmanifest.html on how to enable WAR manifest classpaths on Oracle 9iAS Release 2. (This support is disabled by default.) WebSphere 4.0 also supports manifest classpaths for WAR files, and IBM documentation (see http://www3.ibm.com/software/webservers/appserv/doc/v40/aee/wasa_content/06040.ibm.com/software/webservers/appserv/doc/v40/aee/wasa-content/060401.html) recommends using this when WARs and EJBs reference the same classes. Orion and Oracle will also load manifest classpaths in EARs by default. Note that JBoss/Jetty does not appear to respect manifest classpaths in WARs, so I haven't relied on this non-portable feature in packaging the sample app. The J2EE Reference Implementation also ignores manifest classpaths in WARs. The Servlet 2.3 specification requires that web containers respect the manifest classpaths of library files included in a web app's /WEB-INF/lib directory. However, this is problematic in integrated EAR deployment, as it's unclear what the relative path should be where a WAR is involved. What is the meaning of a relative path from a nested directory inside an archive file? For example, if a . war file is included in the root directory of an EAR, along with the EJBJAR files it references, which of the following two plausible relative paths should libraryJARs use?
../ .. /other-jar-file .jar, which navigates to the WEB-INF directory and then the root of the WAR, and assumes that the library JAR(s) are in the same directory as the root of the WAR.
Neither alternative works in JBoss/Jetty. Thus using manifest classpaths in JARs in a /WEB-INF/lib directory is not portable. Perhaps for this reason, the J2EE 1.3 specification (section 8.1.1.2) suggests that it is necessary to include shared libraries in the /WEB-INF/lib directory even if they are included elsewhere in the same EAR file. of theJ2EE specification does not require the resolution of classes external to the EAR file, such as libraries installed at a server level. This may work in some servers, but is non-standard. If an app depends on external binaries, it's usually better just to use your server's way of installing binaries at server-wide level. (This is also non-portable, but simpler). Despite these limitations, the J2SE Extension Mechanism Architecture has important implications for J2EE app packaging. It allows an approach to J2EE packaging in which we use multiple JAR files to avoid the need to include the same class definitions in multiple modules within an EAR. This is particularly important when app classes depend on in-house or third-party libraries. For example, we can use JAR files containing library classes required by multiple EJBs, while EJB JAR files contain only appspecific EJB implementation classes. See http://www.onjava.com/lpt/a/onjava/2001/06/26/ejb.html for an article by Tyler Jewell of BEA discussing the use of manifest classpaths.
Important |
Especially in EJB JAR files, use J2SE 1.3 manifest classpaths to avoid the need to include the same class definitions in multiple modules. However, remember that not all app servers support manifest classpaths in EAR or WAR deployment units. Also, remember that manifest classpaths only affect where a class definition is held, and do not resolve problems resulting from which class loader first loads a class (for example, the problem of a class loaded by an EJB class loader being unable to see classes within a WAR in many servers). |
It's also possible to try to resolve class loading problems by using the Java Thread API to obtain a class loader programmatically. Section 6.2.4.8 of theJ2EE 1.3 specification requires allJ2EE containers to support the use of the getContextClassLoader() method on java.util.Thread. The J2EE specification isn't entirely clear regarding context class loading. However, the intent appears to be to allow portable classes, such as value objects, to load app classes in whatever container (such as EJB or web container) they may run in. In practice, the context class loader appears to be in the context of the current container. To clarify this behavior, let's consider the effect of the following two calls, made by a helper class that is loaded by the EJB class loader but used in both EJBs and classes running in the web container:
Class.forName (classname): Will use the class loader of the helper class: in this case, the EJB class loader. This means that, if the EJB class loader is the parent of the WAR class loader, the helper will never be able to load classes in the WAR by name.
Many frameworks, such as WebWork, use this approach to avoid problems caused by hierarchical class loading. However, it's not usually required in app code, which should normally only load classes by name using an abstraction layer that should conceal any use of the context class loader.
As no two servers implement class loading in exactly the same way, and class loading behavior can even change between successive releases of the same server, let's conclude with a check list of things that you should find out to understand class loading in your app server:
What is your server's runtime class hierarchy? For example, is the EJB class loader the parent of the WAR class loader?
Although the different behavior of different app servers makes it impossible to advance hard and fast rules where packaging and class loading are concerned, I recommend the following guidelines:
Only include app-specific classes in EJB JAR files and
/WEB-INF/classes directories
EJB JAR files that depend on reusable infrastructure classes should use manifest classpaths to indicate their dependency on other classes within the EAR.
Important |
In complex apps, it can be difficult to devise packaging that is portable across app servers. In some cases portable packaging may add little business value, but may be very time-consuming to achieve. Concentrate on the target app server when packaging apps. But remember the issues discussed above, and especially the likely implications of hierarchical class loaders in apps using EJBs. |
J2EE class loading is a very complex topic. See the following resources for further information:
http://kb.atlassian.com/content/atlassian/howto/classloaders.jsp. Clear, concise description of Orion server's class loading behavior, with references to other resources.
The sample app isn't distributed, and contains a web app and one EJB. Thus we will need to create WAR, EJB JAR, and EAR deployment units, and the app will normally be deployed as an EAR. We use Ant to build each of these deployment units. First we need to understand how to package the generic infrastructure classes the app uses, which may also be used in other apps. As the infrastructure discussed in , , and -notably, the bean factory andJDBC abstraction packages-is used in both EJBs and web components and needs to load classes by name, it's important that it can be packaged so as not to complicate app specific class loading. Thus the framework classes discussed in this tutorial are packaged in four separate JAR files, which can be included in app deployments and referenced usingJ2SE 1.3 manifest classpaths. The implementation and packaging of this framework takes care to ensure that class loading by name will work, even in app servers that use complex class loader hierarchies. The framework classes are divided into the following JARs, built by the /framework/build.xml file in the download, which is invoked by the sample app's build.xml file in the download's root directory. You should adopt a similar multi-JAR strategy if you create your own library packages for use across multiple apps:
i21-core.jar
Core framework packages including the com. interface21. beans package discussed in ; logging support and nested exceptions discussed in ; string, JNDI and other utility classes. None of these classes loads other classes by name, although subclasses of some of them will. This JAR file will be used in both EJBs and web apps. All other JAR files depend on the classes in this JAR.
Framework packages required only in web apps, which should be included only in the /WEB-INF/lib directory of WAR files. This JAR includes:
EJB superclasses and the JNDI bean factory implementation, which is not used by WARs, and so can be loaded by the EJB class loader.
The JDBC abstraction layer and generic data access exception packages discussed in . The classes in this JAR do not use reflection, and are likely to be used by both web apps and EJBs.
All that we need to do to assemble an app is to ensure that the necessary JARs are available to EJB and WAR modules. The same Interface21 JARs required to compile app-specific classes must be available to the relevant deployment unit at runtime. The EAR will contain all the infrastructure JARs except i21-web.jar in the root directory, as follows:
META-INF/ META-INF/MANIFEST.MF META-INF/app.xml i21-core.jar i21-ejbimpl.jar i21-jdbc.jar ticket-ejb.jar ticket.war log4j-1.2.jar
The EJB JAR module uses a manifest classpath declaring its dependence on i21-core.jar, i21-ejbimpl.jar and i21-jdbc.jar, as we've seen above:
Class-Path: log4j-1.2.jar i21-core.jar i21-ejbimpl.jar i21-jdbc.jar
The WAR includes i21-core.jar, i21-web, jar and i21-jdbc.jar in its /WEB-INF/lib directory, as the following partial listing of its contents shows:
WEB-INF/lib/log4j-1.2.jar WEB-INF/lib/jstl.jar Other library classes omitted WEB-INF/lib/i21-core.jar WEB-INF/lib/i21-jdbc.jar WEB-INF/lib/i21-web.jar
In a server such as Orion or WebLogic, in which the EJB class loader is the parent of the WAR class loader,we would only need to include i21-web.jar here. However, in JBoss, which does not use this hierarchicalclass loading structure, all these JARs are required. If we knew that our server supported manifest classpathsin WARs, we could simply declare a manifest classpath in the WAR. The ticket-ejb.jar file and the /WEB-INF/classes directory of the WAR will contain only app-specific classes. Now that we know what must go into our deployment units, we can write Ant scripts to build them. Ant provides useful standard tasks to build WAR and EAR deployment units. The war task is an extension of the jar task, allowing easy selection of deployment descriptors destined for the WAR's WEB-INF directory, JAR files destined for the WEB-INF/lib directory and app classes destined for the /WEB-INF/classes directory. The webxml attribute of the <war> element selects the standard deployment descriptor; the <webinf> sub-element selects other files, such as proprietary deployment descriptors, for inclusion in the /WEB-INF directory; while <lib> and <classes> sub-elements select binaries and classes respectively. The following is the target used in the sample app:
<target depends="build-war">
<war warfile="${web-war.product}" webxml="${web-war.dir}/WEB-INF/web.xml">
<fileset dir="${web-war.dir}" excludes="WEB-INF/**"/> <webinf dir="${web-war.dir}/WEB-INF"> <exclude name="web.xml"/> </webinf>
Here we ensure that app-specific classes, compiled into the directory specified by the classes. dir Ant property, go into the /WEB-INF/classes directory:
<classes dir="${classes.dir}"> <include name="**/* .class"/> </classes>
I've used several <lib> sub-elements of the <war> element to select the runtime libraries required for the different view technologies demonstrated in , as follows:
<lib dir="${lib. dir}/runtime/common" /> <lib dir="${lib. dir}/runtime/jsp-stl" /> <lib dir="${lib. dir}/runtime/velocity" /> <lib dir="${lib. dir}/runtime/xmlc" /> <lib dir="${lib. dir}/runtime/itext-pdf" />
The following subelement includes the Interface21 infrastructure JARs in the /WEB-INF/lib directory. If the app server supported manifest classpaths for WARs, we could omit the first two of these JARs, and provide a manifest referring to these JARs in the WAR's root directory:
<lib dir="${dist.dir}"> <include name="i21-core.jar"/> <include name="i21-jdbc.jar"/> <include name="i21-web.jar"/> </lib> </war> </target>
The Ant EAR task is also easy to use. We simply need to specify the location of the app.xml deployment descriptor and specify the archives to be included using <fileset> sub-elements. In the sample app's directory layout, the deployment descriptor is in the /ear directory, and the WAR and EJB JAR files are in the /dist directory. Note that this task depends on the EJB JAR file and WAR being up to date:
<target depends="ejb-jar, war"> <ear earfile="${app-ear. product}" appxml="ear/app.xml"> <fileset dir="${dist. dir}"> <include name="ticket.war"/> <include name="ticket-ejb.jar"/>
We include the EJB JAR file's dependencies in the root directory of the WAR:
<include name="i21-core.jar"/> <include name="i21-ejbimpl.jar"/> <include name="i21-jdbc.jar"/> </fileset> <fileset dir="lib/runtime/common"> <include name="log4j*.jar"/> </fileset> </fileset> </ear> </target>
The optional Ant EJB tasks are less useful, at least in my experience. Unlike WAR and EAR deployment units, EJB JAR files are ordinary JAR files, with deployment descriptors in the /META-INF directory. Accordingly, I've used the standard jar task to generate the EJB JAR file. This is simply a matter of specifying the contents of the /META-INF directory of the generated JAR file (the standard ejb-jar.xml and any proprietary deployment descriptors) and selecting the classes by setting the basedir attribute to the root of the directory containing the compiled app-specific EJB classes:
<target name="ejb-jar" depends="build-ejb">
<jar jarfile="${ejb-jar. product}" basedir="${ejbclasses. dir}" manifest="${ejb-jar. dir}/manifest" > <metainf dir="${ejb-jar.dir}"> <include name="*/**"/> </metainf> </jar> </target>
Note the use of the manifest attribute of the jar task, which specifies the file to use as the JAR's manifest, enabling us to specify the manifest classpath shown above. Like the WAR target, this EJB target takes all files from the deployment descriptor directory, not just ejb-jar.xml. Proprietary deployment descriptors, such as jboss.xml or weblogic-ejb-jar.xml must also be included in the deployment unit.
Important |
To use the library classes included in this tutorial in your own apps, you will need to include i21-core.jar and either i21-ejbimpl.jar or i21-web.jar, depending on whether you're implementing EJBs or a web module. Note that i21-web.jar should be included in a /WEB-INF/lib directory: it should not be loaded by an EJB class loader. If using the JDBC abstraction described in , which can be used in both EJBs and web apps, i21-jdbc.jar is required. |
All these JAR files are found in the /dist directory of the download. The complete Ant build file discussed above is named build.xml in the root directory of the download.