JBoss EAP / WildFly classloading explained
As mandated by Java EE specifications, an application server should ideally give its deployed applications the freedom to use whatever utility library and whatever version of it, regardless of the presence of concurrent applications that want to use the same library.
This is also known as namespace isolation. However, loading classes from different namespaces can raises some issues which are not easy to solve: for example, what happens if I pack a newer version of an utility library with my application, while an older version of the same library was loaded by the application server? Or also, how can I use two different versions of the same utility library, simultaneously, within the same instance of the application server?
The JBoss/WildFly classloading strategy has changed sensibly through the years: basically, the 4.x releases of the application server used a UnifiedClassLoader, which aimed at reducing communications overhead between running applications, as class data could be shared by reference or simple copies.
One of the major outstanding issues not resolved with the UnifiedClassLoader is classloading dependencies. The idea being that if one application (A) uses the classes of another application (B), the system should know to redeploy A when B gets redeployed, otherwise it will be referencing stale classes. There were actually two different attempts to try to make this work, without the user having to configure anything. Neither attempt really worked and both were dropped.
Since JBoss AS 5.0 was introduced, a new class loader based on the new Virtual File System (VFS). The VFS was implemented to simplify and unify file handling within the application server. The new class loader, named the VFS class loader, uses VFS to locate JAR and class files. Even though this represented a significant change in how classes are loaded in JBoss AS 5.0, the resulting behavior is much the same as for prior versions of JBoss AS.
A common source of errors is including API classes in a deployment that are also provided by the container. This can result in multiple versions of the class being created and the deployment failing to deploy properly.
Class loading in WildFly/JBoss marks a radical departure from previous attempts. Class loading is now based on the JBoss modules project and any application that is deployed is in effect a module. This affirmation raises some questions from a careful reader: which is the module name assigned to a deployed application ? And also, how are dependencies between modules handled by the AS ? Let's answer each question in a separate section
Getting to know module names
Getting to know the module name is not an academic exercise. As a matter of fact, we can establish dependencies between modules, So for many cases, it's needed to know which is the module name assigned to an application.
Therefore, applications that are packaged as top-level archives (such as WAR, JAR, and SAR) are assigned the following module name:
For example, a Web application named WebExample1.war will be deployed as module name:
On the other hand, on applications that contain nested modules (such as the EAR archive), every single archive will be assigned a module name using this classification:
deployment.[ear archive name].[sub deployment archive name]
So, the same Web application, if contained in the archive EnterpriseApp.ear, will be deployed with the name:
Finding the isolation level
The general rule is that every deployed application module is isolated from other modules; this means that, by default, it does not have visibility on the AS modules, nor the AS modules have visibility on the application.
Using the application server modules is, however ,pretty easy and can be summarized in a simple sentence: add a dependency to the required module and the AS will use it. The application server automatically adds dependencies, or they need to be signaled by the user:
- The core module libraries (namely the Enterprise classes) are qualified as implicit dependencies, so they are automatically added to your application, when the deployer detects their usage
- The other module libraries need to be explicitly declared by the user in the application's MANIFEST file or in a custom JBoss's deployment file named jboss-deployment-structure.xml (more about this file in the Advanced deployment strategies section).
Pointing out dependencies for Api that are commonly used by Enterprise application can be tedious. So, as we have anticipated, they are automatically added for you by the application server. Some are added when the application server detects some annotations or configuration files which are typical of that module. For example, adding a beans.xml file triggers the automatic Weld dependency.
The following mind map shows a synthetic view of the modules that are implicitly added to your application:
The meaning of this image is simple: if your application uses any of the core modules indicated, then you don't need specifying any dependency, as the application server is able to link the module automatically.
Modules that are not qualified as implicit dependencies, need to be declared by the user. In our initial example, the log4j library is not mentioned as implicit dependency, so we had to package the log4j JAR along with our application. We can, however, instruct the deployer to use the log4j library, which is bundled in the application server distribution. The simplest and recommended approach to achieve it, is including into the META-INF/MANIFEST.MF the Dependencies: [module] declaration. In our case, to make your application dependent on log4j, just include in your manifest file the following code:
Please note that the module name does not always matches with the package name of the library. The actual module name is specified in the module.xml file by the name attribute of the module element.
Users will typically use the Eclipse (or any other IDE) to update the manifest file, without the need of performing any tedious archive update:
You are not limited to a single dependency, as you can add multiple dependencies separated by a comma. For example, in order to add a dependency on both log4j and Apache Velocity Api, you would use the following:
You can even export the dependencies used by one application module to other applications, by adding the export keyword. For example, in the earlier application, we're now exporting the dependencies to other modules:
Applications that are marked as dependent to the deployment.WebApp.war module will also have access to its dependencies:
The export parameter can also be used to export a dependency to all sub-deployments contained in the ear. Consequently, if you export a dependency from the top-level of the ear (or by a jar in the ear/lib directory) then the dependency will be available to all sub-deployment units as well.
Within the META-INF/MANIFEST.MF, you can also specify additional commands that can modify the server deployer's behavior. For example, the optional attribute can be added to specify that the deployment will not fail, if the module is not found at deployment time.
Finally, when the services keyword is specified, the deployer will try to load services that are placed within the META-INF/services of the archive.
The service Api has become public in Java SE 6. A service can be defined as a set of programming interfaces and classes that provides access to some specific application functionality or feature. A Service Provider Interface (SPI) is the set of public interfaces and abstract classes that a service defines.
You can define a service provider, by implementing the service provider API. Usually, you will create a JAR file to hold your provider. To register your provider, you must create a provider configuration file in the JAR file's META-INF/services directory. When adding the services attribute to your META-INF/MANIFEST.MF, you will be actually able to load the services contained in the META-INF/services directory.
One excellent introduction to the SPI Api is available at: http://java.sun.com/developer/technicalArticles/javase/extensible.
Setting up global modules
This option resembles a bit to the old AS approach for loading common libraries, where you used to place them in the folder JBOSS_HOME/common/lib.
If you define a section named global-modules within your standalone.xml/domain.xml, then you will make the module accessible to other AS modules. For example, instead of declaring a dependency on log4j, you could alternatively use the following section:
<subsystem xmlns="urn:jboss:domain:ee:1.0"> <global-modules> <module name="org.apache.log4j" /> </global-modules> </subsystem>
Although this approach is not generally recommended, as it brings us back to a concept of monolithic application server, it can still yield some benefits. For example, if you are migrating some older applications, and you don't want or simply cannot specify dependencies into the archive.
Advanced deployment strategies
What we have learnt, so far, can be enough for configuring many kind of applications. If you are using a complex archive configuration, such as an EAR archive with several modules and dependencies, it would prove useful to define your classloading strategy in a single file.
The configuration file jboss-deployment-structure.xml can do that exactly this. The advantage of using this file are many:
- You can define the dependencies of all application modules in a single file
- You can load the modules classes using a finer grained manner, by including/excluding all or part of modules
- You can define class loading isolation policy for your applications packaged in an Enterprise archive
Let's see with some practical examples what jboss-deployment-structure.xml can do for you.
Setting up a single module dependency
We have already learnt how to activate a log4j dependency, using the Dependencies attribute in the archive's MANIFEST file. The same effect can be achieved by using the jboss-deployment-structure.xml file. Let's recap the archive structure, which is basically made up of a Web application named WebApp.war.
As you can see, the file jboss-deployment-structure.xml needs to be placed within the META-INF folder of the EAR.
Here's its content:
<jboss-deployment-structure> <sub-deployment name="WebApp.war"> <dependencies> <module name="org.apache.log4j" /> </dependencies> </sub-deployment> </jboss-deployment-structure>
The file jboss-deployment-structure is not for exclusive use of EARs; as a matter of fact, you could deploy it also within the WebApp application, by placing it within the WEB-INF folder of the archive. It is, however, applicable only as top-level archive. Thus if a jboss-deployment-structure.xml is placed in the WAR's WEB-INF folder and the WAR is packaged in a EAR archive, then the jboss-deployment-structure.xml is ignored.
The relevant part of this file is the sub-deployment element, which references the Web application, including within it, the dependencies element. The expected outcome is that the application server will trigger the dependency to log4j Api, which will be therefore visible by our Web application.
Excluding the server automatic dependencies
Earlier in this chapter, we have shown how the application server is able to trigger some dependencies, automatically, when some conditions are met. For example, if you deploy a JSF application (containing the faces-config.xml file), then the JSF 2.1 Api implementation is automatically added.
This might not be always the desired option, for example, because you want to provide another release implementation for that module. You can easily achieve this using the exclusion section in the jboss-deployment-structure.xml, as shown here:
<jboss-deployment-structure> <deployment> <exclusions> <module name="javax.faces.api" /> <module name="com.sun.jsf-impl" /> </exclusions> <dependencies> <module name="javax.faces.api" slot="1.2"/> <module name="com.sun.jsf-impl" slot="1.2"/> </dependencies> </deployment> </jboss-deployment-structure>
Notice in the dependencies section, we have added our alternate JSF 1.2 implementation, which will be used by your application. Actually this JSF implementation ships with the application server distribution along with the javax.faces.api module path, under the folder specified by the slot attribute. In our case, this corresponds to JBOSS_HOME/modules/javax/faces/api/1.2 folder.
Isolating sub deployments
Supposing you have an application that is made up of a Web application, an EJB module and a JAR file containing utility classes. All sub deployments are placed at the root of the archive, so that they will be able to see each other. This can be convenient, however, supposing that your Web application contains itself some implementations of the same EJB. That's absolutely possible since Java EE 6 specification allow your Web application to include EJB classes within the WEB-INF/classes or WEB-INF/lib folder.
How does the classloader solves this conflict? The application server classloader has a priority list when loading classes that are used to avoid any conflict between loaded classes.
- The maximum priority is given to modules, automatically, by the container, including the Java EE APIs. Libraries that are contained in the modules folder are included in this category.
- Then libraries that are indicated by the user within the MANIFEST.MF of the packaged archive as Dependencies(or in the jboss-deployment-structure.xml file).
- Next, libraries that are packed within the application itself, such as classes contained in WEB-INF/lib or WEB-INF/classes.
- Finally, libraries that are packed within the same EAR archive (in the EAR's lib folder).
So, in this example, the EJB libraries located in the WEB-INF folder will "hide" the implementations of EJB.jar top-level deployment. Whether or not this is the desired action from the container, you can still override it:
<jboss-deployment-structure> <ear-subdeployments-isolated>false</ear-subdeployments-isolated> <sub-deployment name="WebApp.war"> <dependencies> <module name="deployment.App.ear.EJB.jar" /> </dependencies> </sub-deployment> </jboss-deployment-structure>
In this example, we have added a dependency to the EJB.jar, which is placed at the root of the archive, which will override the implementation packed within the Web application.
Notice the ear-subdeployments-isolated element placed at the top of the file. By setting the EAR isolation level, you will be able to indicate if the sub-deployments modules are visible to each other.
The default value for this attribute is false, meaning that the sub-deployment modules will be able to see each other. If you are setting isolation to true each module will be then picked up by a different classloader, so, in our example, the Web application will not be able to find the classes contained in EJB.jar and Utility.jar library.
If you want to keep deployment isolated, but allow visibility for some of them, then you have several choices available:
- Move the library to the EAR/lib folder, so that it will be picked up as a separate module
- Specify a dependency using Dependencies or using Class-Path in the MANIFEST.MF file of the calling application
From the following screenshot, you can see how you could correctly set up your EAR, by placing common libraries in the lib folder, and adding a dependency to the EJB classes:
And this is the corresponding configuration required in jboss-deployment-structure.xml:
<jboss-deployment-structure> <ear-subdeployments-isolated>true</ear-subdeployments-isolated> <sub-deployment name="WebApp.war"> <dependencies> <module name="deployment.App.ear.EJB.jar" /> </dependencies> </sub-deployment> </jboss-deployment-structure>
Packaging libraries in a shared library is available, since Java EE 5 is commonly used for holding the JAR files that are used by all modules of the EAR.
The default name for the shared library is lib, however you can override it at any time using the library-directory element in the META-INF/application.xml file. For example, supposing you wanted to use the folder common for holding your shared library, then you could add to your application.xml:
As a side note, we suggest you to avoid placing component-declaring annotations (such as EJB3) in the shared folder, as it can have unintended and undesirable consequences on the deployment process. For this reason, we strongly recommend placing just utility classes in the shared library folder.
Using Class-Path declaration to solve dependencies:
Until now, we have solved dependencies between modules using JBoss' way, which we obviously suggest as first choice. Nevertheless, we should account also for Java's portable way to reference one or more library included in the EAR file.
This can be done using the Class-Path attribute in the MANIFEST.MF file of a module, which needs to reference another library that could not be otherwise be visible to the application (think to the earlier example, of a deployment unit with isolation set to true).
For example, supposing you needed to reference the Utility.jar application from within your Web application, then simply add to your META-INF/MANIFEST.MF the following:
You can actually include more than one library to the Class-Path, keeping separated them by comma, much the same way you did with the JBoss' Dependencies attribute.
Unlike the Dependencies attribute, the Class-Path attribute points to the actual JAR file name (and not the module name) to reference dependant libraries.
Choosing between Class-Path approach and JBoss' Dependencies approach is a matter of how your application is structured: by using JBoss' Dependencies buys you a richer set of options, in particular the ability to export the Dependencies to other deployments, as we have shown earlier. One more point in favor of the JBoss' Dependencies approach, is the ability to reference modules, which are not actually packaged within the application; for example, we have seen how to add a dependency to log4j Api, which are part of the server distribution.
On the other hand, the main advantage of the Class-Path approach relies on application portability. Thus, if a full-portable solution is a priority for you, you could consider switching to the Class-Path manifest attribute.