Skip to content

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+).

License

Notifications You must be signed in to change notification settings

bparry02/jboss-modules-demo

Repository files navigation

JBoss Modules with EAP by Example

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+).

1. Prerequisites

  1. JBoss EAP 6.4 is installed and configured to use standalone.xml (the default)

  2. Maven is installed, and configured to use the JBoss Quickstarts

  3. Optionally, JBoss Developer Studio is installed

2. Beginner

  • 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)

3. Intermediate

  • Deploy an application that depends on a dynamic module (JAR)

  • Deploy an application that depends on an advanced dynamic module (EAR)

4. Advanced

  • Limit class conflicts in EAR Subdeployments

5. Simple application with no 3rd party dependencies

  1. Deploy the Helloworld quickstart

    $ cd initial
    $ mvn clean package jboss-as:deploy
  2. The Helloworld quickstart does not contain any dependencies on third-party libraries

Tip
TODO: verify with module logging

6. Simple application with dependencies bundled

Tip
TODO: Bundle a dependency in helloworld quickstart. Compare module logging

7. Application bundles jar that JBoss provides as module

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.

  1. 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>
  2. Add code to the servlet to display the current month

    HelloWorldServlet.java
    import 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);
        }
    }
  3. Build, deploy, and test the application. The servlet should display the current month: http://localhost:8080/jboss-helloworld/HelloWorld

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

8. Application depends on static module provided by JBoss

  1. 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>
  2. 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
  3. 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>
    Note

    Joda 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.
    Tip

    You 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
  4. Build, deploy, and test the application. It should be successful. The jboss-deployment-structure.xml file specifies that the classes contained in the org.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.

9. Application depends on custom static module

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.

  1. 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 as MonthDay. Change the version of joda-time in the project’s pom.xml to 2.4. Leave the scope as provided.

    pom.xml
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.4</version>
        <scope>provided</scope>
    </dependency>
  2. Create a method that uses the MonthDay class, and returns a String value that will be displayed.

    HelloWorldServlet.java
    import 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 ...
    }
  3. Build and deploy the application. It will deploy successfully.

  4. 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)
    Note
    This 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.
  5. 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:

    1. Package the joda-time jar within the WAR, and remove the dependency on the container-provided module.

    2. Create a custom static module and change the jboss-deployment-structure.xml to depend on that.

  6. 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
  7. 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>
  8. Use Maven to resolve and copy the dependencies to the target folder, and copy joda-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/
  9. Update the jboss-deployment-structure.xml to use the correct module name and slot.

    <dependencies>
        <module name="org.joda.time" slot="2.4" />
    </dependencies>
  10. 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).

10. Advanced: Limiting class conflicts in EAR Subdeployments

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.

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

  2. 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"
  3. The servlet invokes a class in common-utils.jar to display the Hello ::World::! header. Below, a message shows that the org.apache.commons.lang3.StringUtils class was found on the classpath.

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

  5. 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>
  6. 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.

About

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+).

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published