- 1. Prerequisites
- 2. Beginner
- 3. Intermediate
- 4. Advanced
- 5. Simple application with no 3rd party dependencies
- 6. Simple application with dependencies bundled
- 7. Application bundles jar that JBoss provides as module
- 8. Application depends on static module provided by JBoss
- 9. Application depends on custom static module
- 10. Advanced: Limiting class conflicts in EAR Subdeployments
A step-by-step walkthrough of how class loading works using JBoss Modules in Red Hat Enterprise Application Platform 6.4 (also applies to JBoss AS 7 and Wildfly 8+).
-
JBoss EAP 6.4 is installed and configured to use standalone.xml (the default)
-
Maven is installed, and configured to use the JBoss Quickstarts
-
Optionally, JBoss Developer Studio is installed
-
Getting started and understanding module logging
-
Deploy an application that does not depend on any 3rd party libraries
-
(TODO) Deploy an application that includes 3rd party library JARs
-
Deploy an application that depends on a custom static module
-
-
Deploy an application that includes a JAR that is also available as a static module
-
Deploy an application that depends on a static module provided by JBoss
-
Deploy an application that depends on a custom static module (instead of one provided by JBoss) (TODO may not be necessary once getting started is complete)
-
Deploy an application that depends on a dynamic module (JAR)
-
Deploy an application that depends on an advanced dynamic module (EAR)
-
Deploy the Helloworld quickstart
$ cd initial $ mvn clean package jboss-as:deploy
-
The Helloworld quickstart does not contain any dependencies on third-party libraries
Tip
|
TODO: verify with module logging |
Tip
|
TODO: Bundle a dependency in helloworld quickstart. Compare module logging |
The modular class loading in JBoss EAP means that most JARs included in the JBoss installation are not automatically added to the application’s classpath. This section demonstrates that an application can include a JAR that is part of the JBoss installation and there will be no class conflicts.
-
Add dependency to joda-time:1.6.2 (same as version included as JBoss module) with compile scope. This will include the jar in the war
pom.xml<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>1.6.2</version> </dependency>
-
Add code to the servlet to display the current month
HelloWorldServlet.javaimport java.util.Locale; import org.joda.time.LocalDate; // ... snip ... @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter writer = resp.getWriter(); writer.println(PAGE_HEADER); writer.println("<h1>" + helloService.createHelloMessage("World") + "</h1>"); writer.println("Current month is: "+ getMonthText(new LocalDate())); writer.println(PAGE_FOOTER); writer.close(); } // invoke the joda-time library to demonstrate class loading with JBoss Modules public String getMonthText(LocalDate date) { return date.monthOfYear().getAsText(Locale.ENGLISH); } }
-
Build, deploy, and test the application. The servlet should display the current month: http://localhost:8080/jboss-helloworld/HelloWorld
-
Verify the jar exists in the war.
$ mvn clean package $ ls target/jboss-helloworld/WEB-INF/lib/ joda-time-1.6.2.jar
As you can see, even though JBoss contains a module for joda-time, JBoss uses the version included in the WAR since no dependency is declared on the static module.
Tip
|
TODO: Show module logging here |
-
Change the scope of the joda-time maven dependency to
provided
.pom.xml<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>1.6.2</version> <scope>provided</scope> </dependency>
-
Build and deploy the application. It should fail.
22:12:00,743 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-9) MSC000001: Failed to start service jboss.deployment.unit."jboss-helloworld.war".POST_MODULE: org.jboss.msc.service.StartException in service jboss.deployment.unit."jboss-helloworld.war".POST_MODULE: JBAS018733: Failed to process phase POST_MODULE of deployment "jboss-helloworld.war" ... Caused by: java.lang.ClassNotFoundException: org.joda.time.LocalDate from [Module "deployment.jboss-helloworld.war:main" from Service Module Loader]
This is expected becuase provided scope tells Maven not to include the dependency in the WAR, because it will be provided at runtime by some other means. We can verify the JAR is not included by checking the WEB-INF/lib directory. It is not found since this WAR no longer includes any dependencies.
$ ls target/jboss-helloworld/WEB-INF/lib $ ls: cannot access target/jboss-helloworld/WEB-INF/lib/: No such file or directory
-
Add a
jboss-deployment-structure.xml
that specifies a dependency on the joda-time module.src/main/webapp/WEB-INF/jboss-deployment-structure.xml<?xml version="1.0" encoding="UTF-8"?> <jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2"> <deployment> <dependencies> <module name="org.joda.time" /> </dependencies> </deployment> </jboss-deployment-structure>
NoteJoda time is an
unsupported
module. This is for demonstration purposes only. In production you should deploy and maintain your own module instead of depending on any private modules.19:55:04,222 WARN [org.jboss.as.dependency.unsupported] (MSC service thread 1-2) JBAS015868: Deployment "deployment.jboss-helloworld.war" is using an unsupported module ("org.joda.time:main") which may be changed or removed in future versions without notice.
TipYou can use the following commands to see which modules are marked public, private, or unsupported:
$ cd $JBOSS_HOME/modules/system/layers/base # print private and unsupported modules $ find . -name module.xml | xargs grep -l property | sort # print public modules $ find . -name module.xml | xargs grep -L property | sort
-
Build, deploy, and test the application. It should be successful. The
jboss-deployment-structure.xml
file specifies that the classes contained in theorg.joda.time
static module be available to the application at runtime. JBoss will not provide access to this module unless it is specified in the jboss-deployment-structure.xml.
Since joda-time is unsupported, we should deploy our own module—that we will maintain—and depend on it instead. In fact, it is a good practice to create and maintain any static modules you may need, so you can upgrade JBoss with less risk. This applies to most third-party modules. However, if an application depends on container-provided functionality, like JBoss Logging, it should depend on the JBoss-provided module.
-
Let’s upgrade our dependency on joda-time to the latest version (
2.4
at the time of this writing). That way we can use new features, such asMonthDay
. Change the version of joda-time in the project’spom.xml
to 2.4. Leave the scope asprovided
.pom.xml<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.4</version> <scope>provided</scope> </dependency>
-
Create a method that uses the
MonthDay
class, and returns aString
value that will be displayed.HelloWorldServlet.javaimport java.util.Date; import java.util.Locale; import org.joda.time.LocalDate; import org.joda.time.MonthDay; // ... snip ... @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter writer = resp.getWriter(); writer.println(PAGE_HEADER); writer.println("<h1>" + helloService.createHelloMessage("World") + "</h1>"); writer.println("Current month is: "+ getMonthText(new LocalDate()) + "<br>"); writer.println("Abbreviation is: "+ getMonthShortText(new Date())); writer.println(PAGE_FOOTER); writer.close(); } // invoke the joda-time library to demonstrate class loading with JBoss Modules public String getMonthShortText(Date date) { return MonthDay.fromDateFields(date).monthOfYear().getAsShortText(); } // ... snip ... }
-
Build and deploy the application. It will deploy successfully.
-
Test the servlet. A
ClassNotFoundException
is thrown:java.lang.ClassNotFoundException: org.joda.time.MonthDay from [Module "deployment.jboss-helloworld.war:main" from Service Module Loader] org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:213)
NoteThis is an example showing that some class path errors will not be detected until runtime. It is important to test all paths of the application to verify the application’s dependencies are configured properly. -
The application is still depending on the JBoss-provided joda-time module, which is an older version without the
MonthDay
class. There are two simple ways we can solve this problem:-
Package the joda-time jar within the WAR, and remove the dependency on the container-provided module.
-
Create a custom static module and change the jboss-deployment-structure.xml to depend on that.
-
-
Let’s choose to create a custom static module. Create a directory for the module.
$ mkdir -p $JBOSS_HOME/modules/org/joda/time/2.4
-
Create a module.xml file. Notice the
slot
attribute. The slot attribute distinguishes multiple modules of the same name in the JBoss Modules runtime.$JBOSS_HOME/modules/org/joda/time/2.4/module.xml<?xml version="1.0" encoding="UTF-8"?> <module xmlns="urn:jboss:module:1.3" name="org.joda.time" slot="2.4"> <resources> <resource-root path="joda-time-2.4.jar"/> </resources> </module>
-
Use Maven to resolve and copy the dependencies to the
target
folder, and copyjoda-time-2.4.jar
to the module directory.$ mvn dependency:copy-dependencies $ cp target/dependency/joda-time-2.4.jar $JBOSS_HOME/modules/org/joda/time/2.4/
-
Update the
jboss-deployment-structure.xml
to use the correct module name and slot.<dependencies> <module name="org.joda.time" slot="2.4" /> </dependencies>
-
Build, deploy and test. The test is successful! Notice that no warnings about unsupported modules are printed in the logs.
Note
|
We can choose any name for the module, slot, or directory within modules . By convention, we name the module similarly to the package or Maven coordinates. The slot name we use here is the version, since a main module for joda-time already exists (with the same name).
|
Sometimes more control over the classpath is needed for subdeployments (WARs or JARs) within an EAR. This control can be attained by using the subdeployment element of the jboss-deployment-structure.xml.
In this walkthrough we will see how to hide EAR/lib JARs that may be causing a conflict with a bundled WAR.
-
We begin with an EAR that contains a WAR and some library JARs. The WAR depends on the common-utils.jar. The common-utils.jar depends on commons-lang3.jar for some operation.
-
Build the project and deploy the ear file located at
ear-subdeployment/application-ear/target/application-ear.ear
.$ cd ear-subdeployment $ mvn clean package $ $JBOSS_HOME/bin/jboss-cli.sh -c "deploy application-ear/target/common-module.ear"
-
The servlet invokes a class in common-utils.jar to display the
Hello ::World::!
header. Below, a message shows that theorg.apache.commons.lang3.StringUtils
class was found on the classpath. -
For illustration purposes, let’s decide that having the commons-lang StringUtils class visible to the WAR is undesirable. This can happen if class loading conflicts occur, such as ClassCastExceptions. There are many ways of solving this problem using JBoss Modules, but for this example, let’s say both JARs must remain in the EAR’s lib directory.
-
To solve our class loading issue, we want to hide the
org.apache.commons.lang3
package from the WAR. To do this we create a jboss-deployment-structure.xml in the EAR’s META-INF directory.ear-subdeployment/application-ear/src/main/application/META-INF/jboss-deployment-structure.xml<?xml version="1.0" encoding="UTF-8"?> <jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2"> <sub-deployment name="jboss-helloworld.war"> <!-- By default, a dependency on the EAR's lib directory will be provided. The EAR parent module must be explicitly excluded, or our changes will have no effect. --> <exclusions> <module name="deployment.application-ear.ear"/> </exclusions> <!-- We want to depend on the rest of the classes in the EAR's lib, so re-add a dependency on the EAR parent module, but exclude the class/package causing problems. --> <dependencies> <module name="deployment.application-ear.ear"> <imports> <exclude path="org/apache/commons/lang3"/> </imports> </module> </dependencies> </sub-deployment> </jboss-deployment-structure>
-
Build and deploy the application. Observe that the class search message now displays not found since the StringUtils was not found on the classpath of the WAR. However, the common-utils.jar was still able to invoke StringUtils to create the header.