I’ve recently been working on writing a Mbean for use in JBoss’ JMX Console. I was surprised how few pages were available that gave a step-by-step guide on how to do it. So, having gotten one working, I thought I’d share my findings on how to write a MBean for JBoss.

Problem Description

First, let’s describe the problem I’m solving in this example. JBoss provides some excellent pieces for monitoring your server’s health. One of the things I wanted to monitor was the various memory pools within the JVM (PermGen, Eden space, etc). I also needed an external monitoring to monitor this for me, and to send alerts when certain conditions are reached. This package we are using is able to read attributes from JMX Mbeans, so this was the means I decided to try using. If you go into the JBoss JMX console (at http://server/jmx-console) , and look under the jboss.system section, you will see an entry called type=ServerInfo.

jmx-console1.jpg

If you click on it, and invoke the listMemoryPools() operation, you will see a highly useful report with ASCII graphs showing how the memory is going.

jmx-console2.jpg Well, highly useful for a human reader, but terrible for a piece of software to consume. If you look at the listMemoryPools() operation, you will see it returns a java.lang.String. This string contains the entire report, with the graphs, with embedded HTML tags. So our monitoring software would not be able to use this data. In addition, the monitoring software is only able to read attributes from MBeans, but cannot invoke operations. So even if it could use the string value, it could not request it. So I decided to write a custom MBean that invokes the listMemoryPools() operation on JBoss’s MBean, parses the string, and exposes the numeric data as attributes that the monitoring software could read.

Goal - a .SAR file

In order to deploy the MBean to the JBoss server, it is necessary to make an archive called a SAR file. A SAR file is just another JAR file with a specific file organization (in the same manner as a WAR or an EAR). For a SAR, the file contains classes, and a META-INF directory that contains a manifest (nothing special about the manifest) and a special file called jboss-service.xml. This is the deployment descriptor that tells JBoss that the classes in the SAR are a MBean definition. So, this is what I wanted to build.

Starting the Project

First, I created a new Java Project (I use Eclipse). I created a new folder under the project called extlib1 and
copied jboss-jmx.jar and jboss-system.jar from my ${JBOSS_HOME}/lib directory. These two jars are required for compilation, but should not be packaged in the final SAR file, since they will be in the classpath of the JBoss server itself. I then created my package structure in the src directory, and created a directory called resources which is intended to contain files which must be copied into the final distribution — the perfect place to put files that need to go into the META-INF directory. In the resources/META-INF folder I created a starter jboss-service.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<server>

</server>

I also created an Ant file in the root of the project.

<?xml version="1.0"?><project name="JvmMbean" default="deploy">

<property file="${user.home}/ant.properties" />
<target name="clean" depends="undeploy" description="Cleanup">

	<delete dir="dist" />

	<delete dir="tmp/exploded-sar" />

</target>

<target name="compile" description="Compile the source">

	<mkdir dir="tmp/exploded-sar" />

	<javac srcdir="src" destdir="tmp/exploded-sar" debug="on" deprecation="on">

		<classpath>

			<fileset dir="extlib">

				<include name="*.jar" />

			</fileset>

		</classpath>

	</javac>

</target>

<target name="dist" depends="compile" description="Package the application">

	<copy todir="tmp/exploded-sar">

		<fileset dir="resources" />

	</copy>

	<mkdir dir="dist" />

	<jar jarfile="dist/${ant.project.name}.sar" basedir="tmp/exploded-sar" />

</target>

<target name="deploy" depends="dist" description="Deploy the application">

	<copy todir="${jboss.home}/server/${jboss.config}/deploy">

		<fileset dir="dist" />

	</copy>

</target>

<target name="undeploy" description="Undeploy the application">

    <delete file="${jboss.home}/server/${jboss.config}/deploy/${ant.project.name}.sar" />

</target>

</project>

A couple of things to point out about the Ant script:

  • I use a file called ant.properties in my user home directory to define two Ant properties:
    • jboss.home - the full path to my jboss installation
    • jboss.config - the jboss config to be used (in my case, “default”, which is the “default” in “C:/jboss-4.2.2.GA/server/default/deploy”)
  • The default target compiles, packages, and deploys the SAR file to the Jboss server.
  • The classes are compiled to a temporary staging directory “tmp/exploded-sar”
  • Packaging the SAR file is a simple matter of copying of the resources directory
    to the exploded-sar directory, then making a jar of that exploded-sar directory.

Creating the Source Code

For the actual source itself, you need to follow a few rules for your MBean to work:

  1. You need an interface whose name ends in MBean
  2. Your interface must extend the interface org.jboss.system.ServiceMBean.
  3. You need a concrete class whose name is the same as the interface without
    the MBean suffix
  4. Your concrete class needs to implement your interface
  5. Your concrete class also needs to extend org.jboss.system.ServiceMBeanSupport
  6. Your concrete class must override the getName() method,
    returning a category and name for your MBean. The form of the string should be category:service=someName where category is the heading on the jmx-console under which your MBean will be listed, and someName is a name you choose to call the service for your MBean (I use the same name as the implementation class).

So, our minimal interface is:

package com.mattharrah.svcmgmt;
import org.jboss.system.ServiceMBean;
public interface JvmMemoryPoolServiceMBean extends ServiceMBean {

}

and our minimal concrete class is:

package com.mattharrah.svcmgmt;
import org.jboss.system.ServiceMBeanSupport;
public class JvmMemoryPoolService
	extends ServiceMBeanSupport
	implements JvmMemoryPoolServiceMBean {

	@Override
	public String getName() {

		return "com.mattharrah:service=JvmMemoryPoolService";

	}

}

Deployment Descriptor

The last thing to do before getting a minimal working MBean is to define the MBean by adding a mbean element to the deployment decriptor jboss-service.xml in the resources directory:

<?xml version="1.0" encoding="UTF-8"?>

<server>

    <mbean code="com.mattharrah.svcmgmt.JvmMemoryPoolService"

 	name="com.mattharrah:service=JvmMemoryPoolService">

    </mbean>

</server>

At this point, we can deploy our SAR file using Ant and pull it up at http://localhost:8080/jmx-console:

jmx-console3.jpg Lookit! Our MBean (which at this point does absolutely nothing).

Adding Business Logic

For my particular business problem, I needed to expose as attributes information that I scrape out of the string returned from another MBean operation. So, the first thing I did was get my code to invoke an operation on another MBean. Looking in the jmx-console, I can see that the Mbean I need in this case is the type=ServerInfo MBean under jboss.system. This means that the MBean would have the name jboss.system:type=ServerInfo. The operation name is listMemoryPools, which takes exactly one boolean parameter, to which I pass false so the graphs will be suppressed. The following code from my concrete implementation class does this call for me, with the lines of greatest interest highlighted:


	@SuppressWarnings("unchecked")
	private String getMemoryPoolDump() {
		ObjectName o = null;
		try {
			o = new ObjectName("jboss.system:type=ServerInfo");
		} catch (MalformedObjectNameException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}

		List srvrList = MBeanServerFactory.findMBeanServer(null);
		MBeanServer server = (MBeanServer) srvrList.iterator().next();
		String s = null;
		try {
			s = (String) server.invoke(o, "listMemoryPools",
				new Object[] { Boolean.FALSE },
				new String[] { "boolean" });

		} catch (InstanceNotFoundException e) {
			e.printStackTrace();
		} catch (MBeanException e) {
			e.printStackTrace();
		} catch (ReflectionException e) {
			e.printStackTrace();
		}

		return s;
	}

One thing to note: looking at the jmx-console, one can see the class name of the type=ServerInfo MBean, and its tempting to just instantiate an instance of that class and go. This only partially works: the attributes are readable, but invoking the operation does not give you the results you’d expect. For this reason, you need to actually invoke the MBean through its JMX interface for things to work.

Attributes

Now that we have an MBean that invokes an operation on the other MBean and gets its result, we need attributes. Attributes on your JMX bean are simply getters. A method called getFoo() will be a Foo attribute (capitalized) in the JMX console (for a boolean property, isFoo() also works). Knowing this, it’s time to define a whole mess of attributes for all the various numeric values I expect to return on my MBean. These get defined first on the interface:


	public long getCodeCachePeakInit();

	public long getCodeCachePeakUsed();

	public long getCodeCachePeakCommitted();

	...

	public long getPermGenCurrentCommitted();

	public long getPermGenCurrentMax();

From this point, there is nothing left that is specific to an MBean, just business logic.

  1. I define an in-memory data structure (nested maps) to hold the statistics in a way that will be easy when parsing
  2. I write a method that calls my earlier method for getting the string from the JBoss-supplied MBean, then parses the string that comes back and puts the values into the data structure
  3. I implement all the getter methods from my interface so they will call the routine in step 2, then fetch the appropriate value out of the data structure

And when I build and deploy my MBean, I can invoke it in the jmx-console and see all my attributes:jmx-console4.jpg

So, there it is. You can download the eclipse project and source code if you are interested in looking at it further. Enjoy!


1I generally use a lib directory for jars that are required at compile/run-time and which should be packaged with my final distribution, and extlib for those which are only required by my source at compile-time, but which will be found at run-time in my server’s libraries. Examples for this would include common files such as activation.jar, mail.jar,servlet-api.jar, etc.

4 Responses to “Writing a JBoss Mbean, step-by-step”
  1. JavaRant.com says:

    Awesome article here buddy! It is amazing how little there is out there that show in this detail how to write an MBean. Here is a great big atta-boy for ya. :)

  2. Steve says:

    Very useful. Thanks!

  3. Dominic Pereira says:

    Hi, The article is awesome, can u also please help me out on the following.
    1. Is it a good practice to use MBeans for the sake of manageability.
    2. Any good reading specific to JBOSS deployments of MBeans other than the WiKi ones.
    Thanks a million.

  4. Matt says:

    Thanks, Dominic-
    For 1) It’s a fine practice. Whether it’s best for you depends entirely on your organization. If your JBoss Administrators are comfortable using the MBean services either through the JMX Console or some other management tool, then MBeans are a natural fit. If they don’t use such a tool, or can’t find their way around them (I know many system admins who can’t do anything with JBoss except start and stop servers at the command line) they won’t help much.
    2) Surprisingly, no. I haven’t been able to find much useful material at all on doing this, which was a major reason why I wrote the blog entry.
    Thanks again!

Leave a Reply