Module Dependencies in JBoss Enterprise Application Platform 6

In this second article on JBoss modules, Ian Page, Senior Consultant at Tier 2 Consulting, takes a closer look at module dependencies.

Last time you saw how JBoss treats a deployment (in that case, a JAR file) as a module, and how you could create a dependency on it.  In this article I’ll cover some slightly more advanced dependency techniques.

Notes
This article assumes some basic knowledge of JEE (Java Enterprise Edition) applications, and that you have a working JBoss environment available.  A separate Getting Started with JBoss EAP 6 article (coming soon) will cover setting up JBoss.  The examples have been tested on version 6.1.0.GA of JBoss Enterprise Application Platform, although they should work on other revisions of EAP 6 as well as the community edition JBoss AS 7.

If you’ve used the examples from the previous example (and I hope that you have!) please remove them from the deployments directory of your JBoss server as the names may clash.  I could have used different names, but I wanted to keep them short so that it’s easy to type in the URLs!

Eclipse projects containing the source code for this article are included at the end.  They use Maven as a build tool so some familiarity with it will be useful.  However you only need to deploy the archives for this article; it’s not necessary to build from the source.  I’ve included it just for completeness.

Hierarchical Dependencies

You’ve already seen how to create a simple dependency on a JAR by using the Dependencies: entry in the manifest file of your deployment.  What happens if you have dependencies on deployments that themselves have dependencies on other deployments?

I’ve created some classes in different JAR files that have dependencies.  Please note that these classes are useful for demonstration purposes, but they don’t necessarily represent sensible programming practice!

Firstly, in utils.jar there’s a random number generator class that generates a pseudo-random integer between the minimum and maximum values supplied (inclusive).  As in the previous article, I’ve left out some of the less important things such as package names and imports.

RandomInt

public class RandomInt {

private Random random;

public RandomInt() {
this.random = new Random();
}

public int getValue(int min, int max) {
int value = random.nextInt(max - min + 1) + min;
return value;
}
}

Secondly, in area1.jar there’s a calculator class that makes use of the random number generator to calculate the area of a circle with a random radius.

RandomCircleCalculator

public class RandomCircleCalculator {

private static final double PI = 3.1415927;

private int radius;

public RandomCircleCalculator() {
RandomInt randomInt = new RandomInt();
radius = randomInt.getValue(5, 10);
}

public double getArea() {
return PI * radius * radius;
}
}

Finally, in webapp1.war there’s a servlet that accesses the random area calculator.

AreaServlet1

@WebServlet("/calc1")
public class AreaServlet1 extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

RandomCircleCalculator randomCircleCalculator = new RandomCircleCalculator();
double area = randomCircleCalculator.getArea();

PrintWriter writer = response.getWriter();
writer.println("The area of the random circle is " + area);
writer.close();
response.setStatus(SC_OK);
}
}

Obviously these archives have some dependencies, so in the MANIFEST.MF file in the WEB-INF directory of webapp1.jar I’ve added the following entry:

Dependencies: deployment.area1.jar

and in the MANIFEST.MF file in the META-INF directory of area1.jar I’ve added the following entry:

Dependencies: deployment.utils.jar

The architecture of the application is shown below.

Modules2_Architecture1

Download and deploy the three archives utils.jar, area1.jar, and webapp1.war by copying them to the deployments directory of the JBoss server, and start the server if it isn’t already running.  Access the application using the following URL:

http://localhost:8080/webapp1/calc1

If everything has worked correctly, you should see something like the following in your browser:

The area of the random circle is 314.15927

Obviously the value you see will probably vary, because it’s random!

This isn’t a particularly great application though, because we don’t know the radius of the circle from which the area has been calculated.  It would be nice if the servlet could use the random number generator directly, and pass the random radius into a separate calculator.  In fact those classes are already there in the archives that you have just deployed.  There’s a different calculator class in area1.jar, and another servlet in webapp1.war.

CircleCalculator

public class CircleCalculator {

private static final double PI = 3.1415927;

private int radius;

public CircleCalculator(int radius) {
this.radius = radius;
}

public double getArea() {
return PI * radius * radius;
}
}

AreaServlet2

@WebServlet("/calc2")
public class AreaServlet2 extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

RandomInt randomInt = new RandomInt();
int radius = randomInt.getValue(1, 25);

CircleCalculator circleCalculator = new CircleCalculator(radius);
double area = circleCalculator.getArea();

PrintWriter writer = response.getWriter();
writer.println("The area of the circle with radius " + radius + " is " + area);
writer.close();
response.setStatus(SC_OK);
}
}

Try accessing the new servlet from this URL:

http://localhost:8080/webapp1/calc2

Ah, it didn’t work.  You should have got an error like this:

java.lang.ClassNotFoundException: com.tier2consulting.util.RandomInt
    from [Module "deployment.webapp1.war:main"
        from Service Module Loader]
    org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:196)

The servlet can’t see the random number generator.  Although webapp1.war is dependent on area1.jar, and area1.jar is dependent on utils.jar, this doesn’t automatically mean that classes in webapp1.war can see those in utils.jar.  To fix this it would be possible to add another dependency to the webapp1.war, but this could get difficult to manage if there are lots of dependencies.  An alternative is to get area1.jar to export its dependencies so that webapp1.war will inherit them.

Exporting Dependencies

Exporting a dependency is simple: just add exported to the entry in the MANIFEST.MF file, so that it looks like this:

Dependencies: deployment.utils.jar export

You can export some, none, or all of the dependencies, as you like.  I’ve created a new JAR called area2.jar that contains the new exported dependency, and a new web application called webapp2.war that points to the new JAR.  That’s all that I’ve changed; there’s nothing pointing directly to utils.jar (I promise!).

The revised architecture of the application is shown below.

Modules2_Architecture1

Download and deploy the new archives area2.jar and webapp2.war, and then test the new servlet with the following URL:

http://localhost:8080/webapp2/calc2

This time you should see successful output similar to this:

The area of the circle with radius 24 is 1809.5573952

It worked!  The web application is now able to see the classes in utils.jar because they are exported by area2.jar.

Fine Control

The dependency management that you can achieve with the MANIFEST.MF entries are quite coarse; it’s typically an all-or-nothing approach.  If you want to have better control over what’s going on, then you need to use a special JBoss deployment descriptor: the jboss‑deployment‑structure.xml file.  Let’s take a look at an example scenario that builds upon the classes you’ve already seen.

We currently have a web application that gets a random integer from the random number generator, then passes it into the area calculator and displays the result.  However, I want to be more accurate with the calculation, so I’ve created a new area calculator class with a better value for π and placed it in a new JAR file called area3.jar.

New CircleCalculator

public class CircleCalculator {

private static final double PI = 3.141592653589793;

private int radius;

public CircleCalculator(int radius) {
this.radius = radius;
}

public double getArea() {
return PI * radius * radius;
}
}

I still want to reference my old JAR file though, because that’s what is exporting the dependency for the random number generator, which I still need (I could have pointed directly at the utils.jar instead, but that might not be possible in a more complicated, real-life situation).  I’ve created a jboss‑deployment‑structure.xml file that excludes the packages that I don’t want from the old JAR, and adds the dependency for the new JAR.  This file needs to go in the WEB-INF directory of the web archive.  I can then remove the Dependencies: entry from the MANIFEST.MF file.

jboss-deployment-structure.xml

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2">

<deployment>

<dependencies>
<module name="deployment.area2.jar">
<imports>
<exclude path="com/tier2consulting/area" />
</imports>
</module>
<module name="deployment.area3.jar" />
</dependencies>

</deployment>

</jboss-deployment-structure>

The new architecture of the application is shown below.

Modules2_Architecture3

Download and deploy the improved area calculator contained in area3.jar, and the web application with its new deployment descriptor in webapp3.war.  No code has been changed in the web application; the dependency management is handling it all.  Access the web application using the following URL:

http://localhost:8080/webapp3/calc2

You should get output similar to this:

The area of the circle with radius 2 is 12.566370614359172

Notice that the value is accurate to more significant digits than it was before.  The web application is now using the new calculator class.

Conclusion

We’ve been able to control how modules see each other using two different approaches.  This can resolve class loading errors and conflicts that are common in application servers where classes with the same names may be loaded from multiple archives.

In the next article, we’ll take a look at other ways of deploying modules, how we can have different versions of the same module deployed at the same time, and how we can make modules visible to all our deployments without having to explicitly declare dependencies.

Find out more about JBoss and Tier 2 Consulting

Eclipse Projects

modules2-utils.zip
modules2-area1.zip
modules2-area2.zip
modules2-area3.zip
modules2-war1.zip
modules2-war2.zip
modules2-war3.zip