Skip to main content

Extending Spring JMX support

Customize resource management with the Spring framework

Claude Duguay (claude.duguay@verizon.net), Senior J2EE architect, Capital Stream Inc.
Claude is senior J2EE architect at Capital Stream Inc. He has over 25 years of software development experience and started working with the Java platform when the first beta was released. Claude has published more than 75 articles and more than 150 book reviews, primarily focused on the Java language and XML.

Summary:  The Spring framework minimizes architectural dependencies and externalizes composition in your applications, but applications also need to be managed. Fortunately, Spring 1.2 includes sophisticated JMX integration support -- and JMX delivers a practical management infrastructure for your applications. In this article, Claude Duguay takes Spring JMX a step further, showing you how to add notification events to methods and attributes transparently. The resulting code lets you monitor state changes without cluttering up your Java™ objects.

Date:  01 Nov 2005
Level:  Intermediate
Activity:  4790 views

While the Spring framework's JMX management infrastructure is solid right out of the box, it does allow room for customization, particularly as you get into the higher level functionality afforded by Model MBeans. In this article, I use a relatively simple exercise -- adding notification events to Spring-based application methods and attributes -- to help you get comfortable with customizing Spring JMX. After following my example from start to finish, you'll be on the path to fine-tuning Spring's JMX management infrastructure for your application needs.

I'll start with a high-level overview of the JMX API, the Spring framework, and Spring JMX, and then move on to developing the extensions. My first extension lets me configure MBean metadata using an external XML format, which (like Hibernate mapping files) can be stored in the classpath alongside Java objects. My second extension adds a simple naming convention to the ModelMBean class to transparently configure customized notification messages. The new notification messages are triggered when attributes change or either before or after specific methods are called.

I conclude the article with a working example based on a mockup service object with both start and stop methods and a read-write attribute to be managed. I test the implementation with a small client/server application designed for that purpose. The application server is a standard Java 5.0 MBeanServer supplemented with an HTTP adaptor from the MX4J open source project. See Download for the article source and Resources for technology downloads.

Overview of JMX

Java Management Extensions (JMX) is the Java-based standard for managing and monitoring services on the network. At the heart of the JMX API are manageable beans, or MBeans. MBeans provide the instrumentation layer for manageable resources like applications, services, and devices. In a nutshell, MBeans provide a flexible, adaptor-based architecture for exposing the attributes and operations of Java-based (or Java-wrapped) resources. Once exposed, these resources can be monitored and managed using a browser and an HTTP connection or through protocols such as SMTP or SOAP.

Written and deployed MBeans are exposed via MBeanServer instances to bring interactivity to various application views. MBeanServer instances can also be combined into arbitrary federated relationships to form more complex, distributed environments.

The JMX standard offers four MBean variants:

  • Standard MBeans directly implement the methods used to manage an object, either by implementing a programmer-defined interface that has a class name ending in "MBean" or by using a Standard MBean instance, which takes a class as a constructor argument, along with an optional interface class specification. The interface can be used to expose a subset of the object's methods for use in management.

  • Dynamic MBeans dynamically access properties using attribute accessors and invoke methods using a generalized invoke() method. Available methods are specified in an MBeanInfo instance. This approach is more flexible but not as type-safe as Standard MBeans. Coupling is dramatically reduced and manageable POJOs (plain old Java objects) do not need to implement specific interfaces.

  • Model MBeans provide an improved layer of abstraction and extend the Dynamic MBean model to further remove dependencies on a given implementation. This is helpful when multiple versions of the JVM might be at play or when third-party classes need to be managed with looser coupling. The main difference between Dynamic MBeans and Model MBeans is the additional metadata available in Model MBeans.

  • Open MBeans are a restricted version of Model MBeans that reduce types to a fixed set to maximize portability. By restricting data types, more adaptors can be applied and technologies such as SMTP can more easily accommodate the management of Java applications. This variant also specifies standard structures such as arrays and tables for improved compound object management.

Standard MBeans are the easiest variant to implement if you control both the client and the server. They have the advantage of being typed but lose some flexibility when applied in more generalized management console environments. If you plan to use Dynamic MBeans, you may as well make the leap to Model MBeans, which in most cases will improve the abstraction layer with virtually no additional complexity. Open MBeans are the most portable variant and the only way to go if you need to expose compound objects. Unfortunately, the amount of code required to expose compound structures in Open MBeans is prohibitive and only warranted if you require a sophisticated commercial management solution.

JMX also supports notification using an event model with filters and broadcasters. For this to work, Standard MBeans require the declaration of an MBeanInfo metadata description. Standard MBean implementations often construct these internally but they are never seen directly by the developer. Later in this article, you'll see how to use an XML descriptor format for Model MBean metadata and Spring's JMX support to make configuration virtually transparent.


Spring in your step

Like J2EE, the Spring framework delivers many powerful Java development features in a single architecture. Unlike J2EE, Spring's open-ended technology stack offers a wide variety of choices without the burden of dependencies. For example, Spring's object-relational mapping tools can replicate the behavior of Enterprise JavaBeans without introducing the same rigidity. Whereas the EJB spec constraints the approach, Spring provides numerous technology interfaces, allowing you to choose the one that's best for your application scenario or create your own if need be. Similarly, using Spring's dynamic proxy classes to add transactional or security constraints to your Java objects keeps them lean and targeted at the application space rather than infrastructure requirements.

Spring's AOP-enabled, composition-centric (IOC) beans do much of the work of keeping your infrastructure and business objects separate from each other. As a result, crosscutting concerns like logging, transactions, and security no longer intrude on your application code.

IOC (inversion of control) is a key strategy for reducing coupling. Spring's IOC implementation uses dependency injection to effectively "invert control" from your application code to the Spring container. Rather than coupling your classes to the application's object graph at creation time, Spring lets you configure classes and their dependencies using XML or property files (although XML is considered best practice). You then use standard accessors to "inject" references to the objects your classes depend on. You can think of this as externalizing composition, which accounts for a much larger proportion of code than inheritance in a typical application.

AOP is key to managing crosscutting concerns in application development. As implemented in traditional object-oriented programming, these concerns are handled as individual instances, introducing potentially unrelated code (that is, clutter) into application classes. Spring uses the AOP specification and an XML configuration file to externalize crosscutting concerns, thus preserving the purity of your Java objects.


Introducing Spring JMX

JMX support in Spring 1.2 provides automatic MBeanServer registration using easily configured bean proxies and support for the standard JSR-160 remote connectors. At its simplest, you can use Spring JMX to register objects using the MBeanExporter class. Spring automatically recognizes a StandardMBean or wraps a ModelMBean proxy around your object, using introspection by default. The BeanExporter can be used with explicit references to declared beans or you can have it automatically detect beans using the default strategy or a more specific one.

The numerous assemblers delivered with Spring 1.2 enable transparent MBean construction, including the use of introspection, Standard MBean interfaces, metadata (using class-level annotations), and explicitly declared method names. Spring's exporter and assembler-based model is easy to extend and can deliver as much or as little control as you like in creating registered MBeans.

JMX registers and accesses management objects using the ObjectName idiom. Spring provides a variety of naming strategies if you choose to use automatic registration. With a "key" naming strategy, you can use a properties map to associate MBean names to NameObject instances. If you implement the ManagedResource interface you can use a metadata naming strategy. You can also implement your own strategy, thanks to Spring's highly flexible architecture and numerous extension points.

By default, Spring finds a running MBeanServer instance or creates a default instance if one is not already running or explicitly declared. You can instantiate your own MBeanServer directly using Spring configuration and use a variety of connectors just as easily. Spring delivers control over both client and server connections and provides client proxies to facilitate client-side programming.

All of these features are available right out of the box in Spring 1.2. While Spring JMX offers numerous options, the default exporter is enough for many projects, allowing you to get up-and-running very quickly. As you work more with JMX, however, you could notice a number of idiosyncrasies when using implicit MBean construction. As a result, you may gradually migrate from Standard MBeans toward Model MBeans, where you can apply increased control over your application's attributes, operations, and notification metadata. To retain the benefits of loose coupling (and thus the advantages inherent in Spring's flexible architecture), you'll want to implement this control outside of your Java objects.

Spring's IOC makes it easy to wire object dependencies externally and this advantage is easily leveraged in Spring's own architecture. The IOC philosophy of keeping object dependencies injectable makes it a breeze to add, replace, or supplement the behavior of objects, including Spring's JMX support. For the remainder of the article, I'll focus on extending Spring JMX for more fine-grained application management without cluttering up application code or interfering with Spring's inherent flexibility.


Extending Spring JMX

The Spring framework provides many useful tools for handing JMX, including extension points for extending JMX functionality. I'll use these to gain more control over MBeanInfo declarations without imposing annotations on the Java objects. For this I will need to extend Spring JMX in two ways: first so that I can configure MBean metadata using an external XML format (similar to the JBoss microkernel) and second so that I can store the XML files in my classpath along with their associated Java objects (much like Hibernate mapping files).

Recommended reading

The documentation for Spring 1.2 includes a full chapter on using JMX, which you should read if you plan to work with JMX in Spring. See Resources for the Spring 1.2 documentation.

I'll extend the RequiredModelMBean class to use a simple naming convention for finding related MBean deployment descriptors using the format <ClassName>.mbean.xml. Defining such XML-based MBean descriptors improves my control over application metadata without giving up the flexibility of POJO-based designs and Spring composition. To achieve this, I'll implement my own assembler and extend the base Spring JMX exporter. The extended exporter accommodates the creation of an extended ModelMBean that supports the interception of attribute changes, as well as before and after method execution notifications. I could use Spring's AOP mechanism to accomplish this, but ModelMBeans are already proxies to the underlying managed resource, so I'll take the more direct approach of extending the RequiredModelMbean class.

Managing fundamentals

Regardless of the technology you use, there are three major areas of concern when managing resources:

  • Attributes (sometimes called properties, fields, or variables). Read-only attributes are useful for exposing metrics or states. Read/write attributes allow managers to change configuration.

  • Actions (executable calls, methods in the case of Java code). Actions are used to trigger events such as startup and shutdown or other application-specific operations.

  • Events (notification to a monitoring system reflecting changes in state or the execution of some action). Notification confirms that actions or state changes actually took place. Notification can also be used to trigger events, such as reacting to a state change that surpasses a given threshold, as in the case of dwindling resources like memory or disk space. This type of notification can be used to send an e-mail or page to the application administrator when the system requires attention.

As I extend Spring JMX, I'll demonstrate each of the three areas of concern with a simple use case: an example service with start and stop methods and a read-write attribute to be managed. I'll also employ a small client/server application to test my implementation and expose an HTTP management interface using an MX4J adaptor. All of the examples will be necessarily restricted but should be illustrative enough to set you on the right track. You'll see for yourself how easy it can be to add JMX notification events to your Spring-based application methods and attributes, with the result that you can monitor state changes without introducing unnecessary code into your Java objects.

The MBeanInfo model

If you download the code associated with this article, you'll find a package named com.claudeduguay.mbeans.model, which contains a set of classes for modeling an MBeanInfo XML file. The package contains numerous classes so I won't go over them all in detail, but it's worth taking a moment to cover the basics.

The root of the model is the MBeanDescriptor class, which provides a createMBeanInfo() method responsible for creating a JMX-compliant ModelMBeanInfo instance from the application metadata. The MBeanDescriptorUtil class provides a pair of static read() methods that load and save the XML document model. The Document and Element instances used in this model are based on the JDOM framework.

The basic MBeanInfo-related classes and the descriptor model I use are closely correlated. All of the base attributes from the standard classes are modeled in XML and can be defined using a simple format. The <mbean> tag, for example, is the root of my document. It has a direct relationship with the ModelMBeanInfo, which expects a type and description attribute. The type is the fully qualified class name of the managed bean. This class could certainly be derived in-context when using my Spring solution; however, this package is intentionally uncoupled from Spring so that it may be reused in other contexts. The <mbean> tag expects zero or more children of the types attribute, operation, constructor, and notification. The base MBeanInfo class XML attributes are provided for each of these.


Figure 1. MBean XML format
Figure 1. MBean XML Format

The MBean model implementation utilizes a few utility classes associated with JDOM in the package com.claudeduguay.util.jdom. These are primarily interfaces for parsing and building Document and Element objects and a utility class that makes reading and writing Document streams easier. Most of the code you'll be looking at is in the com.claudeduguay.mbeans.spring package.

Enough background work -- let's start extending Spring JMX!


Improving notification in ModelMBeans

The first thing I want to do is extend the Spring JMX ModelMBean implementation so that I can send notifications without implementing the behavior directly in managed resources. For this to work, I'll also need to extend the Spring exporter to create instances of my improved ModelMBean. Finally, I'll need to use a new assembler to fetch MBeanInfo metadata from my mapping files.

The ModelMBeanExtension class

One of my objectives in extending the RequiredModelMBean class is to transparently enable notifications in management proxies. My application requires three kinds of notification: set attribute value, before method invocation, and after method invocation. In each case, the message can be as informative as I like because I'm configuring it myself. To achieve this, I use a naming convention for the notification info type in which the dot-delimited type name is checked for the pattern <matchingType>.<methodOrAttributeName>. The matching type must be one of set, before, or after. If the type is set, I expect an attribute name; otherwise, I assume a method name.

The code for the extended ModelMBean uses additional classes to facilitate a couple of things. The first is a NotificationInfoMap, which is a simple Map constructed from the notification metadata and associated with the prefix (set|before|after) dot name (method|attribute) pattern so that I can get matching notification metadata more efficiently. The second is just a static collection of utility methods. Listing 1 shows the RequiredModelMBean extended for notification:


Listing 1. ModelMBeanExtension

package com.claudeduguay.mbeans.spring;

import java.lang.reflect.*;

import javax.management.*;
import javax.management.modelmbean.*;

public class ModelMBeanExtension extends RequiredModelMBean
{
  protected NotificationInfoMap notificationInfoMap;
  protected ModelMBeanInfo modelMBeanInfo;
  protected Object managedBean;
  
  public ModelMBeanExtension() throws MBeanException {}

  public ModelMBeanExtension(ModelMBeanInfo modelMBeanInfo)
    throws MBeanException
  {
    super(modelMBeanInfo);
    this.modelMBeanInfo = modelMBeanInfo;
    notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
  }
  
  public void setModelMBeanInfo(ModelMBeanInfo modelMBeanInfo)
    throws MBeanException
  {
    this.modelMBeanInfo = modelMBeanInfo;
    notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
    super.setModelMBeanInfo(modelMBeanInfo);
  }
  
  public MBeanNotificationInfo[] getNotificationInfo()
  {
    return modelMBeanInfo.getNotifications();
  }
  
  public void setManagedResource(Object managedBean, String type)
    throws MBeanException, RuntimeOperationsException,
      InstanceNotFoundException, InvalidTargetObjectTypeException
  {
    super.setManagedResource(managedBean, type);
    this.managedBean = managedBean;
  }
  
  protected void maybeSendMethodNotification(
  	String type, String name) throws MBeanException
  {
    MBeanNotificationInfo info = notificationInfoMap.
      findNotificationInfo(type, name);
    if (info != null)
    {
      long timeStamp = System.currentTimeMillis();
      String notificationType = ModelMBeanUtil.
        matchType(info, "." + type + "." + name);
      sendNotification(new Notification(
        notificationType, this, timeStamp,
        info.getDescription()));
    }
  }

  protected void maybeSendAttributeNotification(
    Attribute attribute)
    throws MBeanException, AttributeNotFoundException,
    InvalidAttributeValueException, ReflectionException
  {
    String name = attribute.getName();
    MBeanNotificationInfo info = notificationInfoMap.
      findNotificationInfo("set", attribute.getName());
    if (info != null)
    {
      Object oldValue = getAttribute(name);
      Object newValue = attribute.getValue();
      long timeStamp = System.currentTimeMillis();
      String notificationType = ModelMBeanUtil.
        matchType(info, ".set." + name);
      sendNotification(new AttributeChangeNotification(
        this, timeStamp, timeStamp,
        info.getDescription(), info.getName(),
        notificationType, oldValue, newValue));
    }
  }
  
  public Object invoke(
  	String name, Object[] args, String[] signature)
      throws MBeanException, ReflectionException
  {
    maybeSendMethodNotification("before", name);
    Object returnValue = super.invoke(name, args, signature);
    maybeSendMethodNotification("after", name);
    return returnValue;
  }

  public Object getAttribute(String name) throws MBeanException,
    AttributeNotFoundException, ReflectionException
  {
    try
    {
      Method method = ModelMBeanUtil.findGetMethod(
        modelMBeanInfo, managedBean, name);
      return method.invoke(managedBean, new Object[] {});
    }
    catch (IllegalAccessException e)
    {
      throw new MBeanException(e);
    }
    catch (InvocationTargetException e)
    {
      throw new MBeanException(e);
    }
  }

  public void setAttribute(Attribute attribute)
    throws MBeanException, AttributeNotFoundException,
      InvalidAttributeValueException, ReflectionException
  {
    try
    {
      Method method = ModelMBeanUtil.findSetMethod(
        modelMBeanInfo, managedBean, attribute.getName());
      method.invoke(managedBean, attribute.getValue());
      maybeSendAttributeNotification(attribute);
    }
    catch (InvocationTargetException e)
    {
      throw new MBeanException(e);
    }
    catch (IllegalAccessException e)
    {
      throw new MBeanException(e);
    }
  }
}

No need to proxy proxies!

Because a ModelMBean is already a form of proxy, there's not much point in using Spring's proxy mechanism and AOP interceptors to intercept the methods I'm interested in. The ModelMBean interface requires the implementation of both setAttribute and invoke methods to manage calls into an underlying managed resource. I can subclass the RequiredModelMBean class, guaranteed to be present in any JMX implementation, and add the functionality I need.

My ModelMBeanExtension implements the same constructors but stores a copy of the ModelMBeanInfo in an instance variable. Because this value can be set either by a constructor or by calling the setModelMBeanInfo method, I also override this method to store the value, calling the superclass to complete default behavior. By default, the RequiredModelMBean class adds a couple of generic notification descriptors, so I override the getNotificationInfo() method to return only those I've described. Generic notifications can still be sent, but clients asking for a list of notifications will not see them listed.

To send notifications, I override the setAttribute() and invoke() methods and check whether a call matches one of my notification info descriptors. Traversing the list each time is unlikely to impose tremendous overhead because most classes will send only a limited set, but I need to test each notification's potentially many notification type strings, and repeatedly doing this seems like a waste of cycles. To make sure I don't run into any performance issues, I instantiate a notification info map, a name/info map that I can use for rapid lookups. The key is a simple string with the type prefix (set, before, or after) and the attribute or method name involved. I can use a findNotificationInfo() method to find a notification info instance during a setAttribute() call or method invocation.

With the infrastructure in place, I can intercept calls to setAttribute() and the invoke() method. Attribute changes may require me to send an AttributeChangeNotification instance, which expects the old attribute value and the new value, along with details I can get from the notification info descriptor. Whenever I send a notification, I want to send a sequence number that allows client applications to sort the messages, should they show up out of order. To make this easy, I use a current timestamp instead of managing a counter. Once the notification object is created, the sendNotification() method ensures it is delivered. The same ideas are applied to the invoke() method, though here I use the simpler Notification object. I can check both (before and after) by calling the invoke() method in the superclass and sending before and after notifications, depending on what I find.


Extending the Spring JMX exporter

To use the extended ModelMBean, I need to override the createModelMBean() method in the Spring MBeanExporter. Because it is possible to inject the assembler property, I must be sensitive to the fact that it may not be the one I expect. I can set the assembler I want in the constructor, but I need to return a normal ModelMBean if the assembler is changed. All I have to do is cache a local reference to the MBeanInfoAssembler and check to see what type it is when a new ModelMBean is created. Listing 2 shows all of these changes:


Listing 2. MBeanDescriptorEnabledExporter

package com.claudeduguay.mbeans.spring;

import javax.management.*;
import javax.management.modelmbean.*;

import org.springframework.jmx.export.*;
import org.springframework.jmx.export.assembler.*;

public class MBeanDescriptorEnabledExporter extends MBeanExporter
{
  protected MBeanInfoAssembler mBeanInfoAssembler;
  
  public MBeanDescriptorEnabledExporter()
  {
    setAssembler(new MBeanDescriptorBasedAssembler());
  }
  
  public ModelMBean createModelMBean() throws MBeanException
  {
    if (mBeanInfoAssembler instanceof MBeanDescriptorBasedAssembler)
    {
      return new ModelMBeanExtension();
    }
    return super.createModelMBean();
  }
  
  public void setAssembler(MBeanInfoAssembler mBeanInfoAssembler)
  {
    this.mBeanInfoAssembler = mBeanInfoAssembler;
    super.setAssembler(mBeanInfoAssembler);
  }
}

When using this extended class, I can change the assembler using the standard Spring idiom and fall back to default behavior if I need to. In most cases, this variant won't be worth using if I end up bypassing the extensions. If I want to use the extended ModelMBean with a new custom assembler, however, I am now free to do so.


Building a custom assembler

My custom assembler's primary duty is to find the metadata mapping file, relative to the class being managed. Having found the file, I load it and produce the requisite ModelMBeanInfo instance. To do this, I simply implement the Spring MBeanInfoAssembler interface, build the classpath-relative path to the file, load it using the static MBeanDescriptorUtil.read() method, and return the results, as shown in Listing 3:


Listing 3. MBeanDescriptorBasedAssembler

package com.claudeduguay.mbeans.spring;

import java.io.*;

import javax.management.modelmbean.*;

import org.springframework.core.io.*;
import org.springframework.jmx.export.assembler.*;

import com.claudeduguay.mbeans.model.*;

public class MBeanDescriptorBasedAssembler
  implements MBeanInfoAssembler
{
  public ModelMBeanInfo getMBeanInfo(
    Object managedBean, String beanKey)
  {
    String name = managedBean.getClass().getName();
    String path = name.replace('.', '/') + ".mbean.xml";
    
    ClassPathResource resource = new ClassPathResource(path);
    InputStream input = null;
    try
    {
      input = resource.getInputStream();
      MBeanDescriptor descriptor = MBeanDescriptorUtil.read(input);
      return descriptor.createMBeanInfo();
    }
    catch (Exception e)
    {
      throw new IllegalStateException(
        "Unable to load resource: " + path);
    }
    finally
    {
      if (input != null)
      {
        try { input.close(); } catch (Exception x) {}
      }
    }
  }
}

My MBeanDescriptorBasedAssembler ignores the bean key argument and creates the required ModelMBeanInfo instance directly from the managed bean reference.


A working example

For the remainder of the article, I'll focus on demonstrating the use of my Spring JMX extensions. For this purpose, I'll use an imagined service that exposes a couple of methods and an attribute, thus illustrating a typical use case scenario.

ExampleService is a Java object that merely prints to the console when called, as shown in Listing 4:


Listing 4. ExampleService
  
package com.claudeduguay.jmx.demo.server;

public class ExampleService
{
  protected String propertyValue = "default value";
  
  public ExampleService() {}
  
  public String getPropertyValue()
  {
    System.out.println("ExampleService: Get Property Value");
    return propertyValue;
  }

  public void setPropertyValue(String propertyValue)
  {
    System.out.println("ExampleService: Set Property Value");
    this.propertyValue = propertyValue;
  }

  public void startService()
  {
    System.out.println("ExampleService: Start Service Called");
  }

  public void stopService()
  {
    System.out.println("ExampleService: Stop Service Called");
  }
}

Administrator-friendly messages

My extended descriptor can map the attribute and operations almost directly. The primary advantage of descriptor methods over introspective approaches is the ability to provide more specific messages. Configuration choices for notification descriptors will hinge on the naming convention for the type's (XML) attribute. The actual names are arbitrary, but my code triggers on the set.name, before.name, and after.name patterns in the types. In this case, I associate a set notification with the propertyValue (JMX) attribute and both before and after notifications for the startService() and stopService() methods. Again, these extensions let me make good use of descriptive messages.

In Listing 5, you can see a property and two methods defined. The notification descriptors define both before and after events for both methods and a property setting notification:


Listing 5. ExampleService.mbean.xml
  
<?xml version="1.0"?>
<mbean name="ExampleService" description="Example Service"
  type="com.claudeduguay.jmx.demo.server.ExampleService">

  <attribute name="propertyValue"
    description="Property Value Access" type="java.lang.String"
    readable="true" writable="true" />

  <operation name="stopService"
    description="Stop Example Service" />
  <operation name="startService"
    description="Start Example Service" />

  <notification name="PropertyValueSet"
    types="example.service.set.propertyValue"
    description="PropertyValue was set" />
  <notification name="BeforeStartService"
    types="example.service.before.startService"
    description="Example Service is Starting" />
  <notification name="AfterStartService"
    types="example.service.after.startService"
    description="Example Service is Started" />
  <notification name="BeforeStopService"
    types="example.service.before.stopService"
    description="Example Service is Stopping" />
  <notification name="AfterStopService"
    types="example.service.after.stopService"
    description="Example Service is Stopped" />
  
</mbean>


Configuring the server

To run the example in a client/server environment, I need to configure and start an MBeanServer instance. For this I'll rely on the Java 5.0 MBeanServer instance, which ensures I'll be able to use exposed management extensions on the JVM and manage my own code at the same time. I could also run multiple instances of the MBeanServer if I so chose; you might try this yourself as an exercise if you like.

Like Java 5.0, the Spring framework allows you to configure your own MBeanServer instances. I've chosen to use Java 5.0 for its support for JSR-160 connectors, which I'll need for my client code.


Listing 6. SpringJmxServer
  
package com.claudeduguay.jmx.demo.server;

import org.springframework.context.*;
import org.springframework.context.support.*;

import mx4j.tools.adaptor.http.*;

/*
 * To use the SpringJmxServer, use the following command line
 * arguments to activate the Java 1.5 JMX Server.
 * 
 * -Dcom.sun.management.jmxremote.port=8999
 * -Dcom.sun.management.jmxremote.ssl=false
 * -Dcom.sun.management.jmxremote.authenticate=false
 */

public class SpringJmxServer
{
  public static void main(String[] args)
    throws Exception
  {
    String SPRING_FILE =
      "com/claudeduguay/jmx/demo/server/SpringJmxServer.xml";
    ApplicationContext context =
      new ClassPathXmlApplicationContext(SPRING_FILE);
    HttpAdaptor httpAdaptor =
      (HttpAdaptor)context.getBean("HttpAdaptor");
    httpAdaptor.start();
  }
}

The Spring configuration file for the server is very simple, thanks to my MBeanDescriptorEnabledExporter. In addition to declaring the ExampleService, I add the MX4J entries required to expose an HTTP adaptor and connect the XSLTProcessor to the HttpAdaptor. Note that this is one area where Spring's IOC implementation comes in very handy. Listing 7 shows the Spring configuration file for my SpringJmxServer instance:


Listing 7. SpringJmxServer.xml
  
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="exporter" class=
    "com.claudeduguay.mbeans.spring.MBeanDescriptorEnabledExporter">
    <property name="beans">
      <map>
        <entry key="Services:name=ExampleService"
          value-ref="ExampleService" />
        <entry key="MX4J:name=HttpAdaptor"
          value-ref="HttpAdaptor" />
        <entry key="MX4J:name=XSLTProcessor"
          value-ref="XSLTProcessor" />
      </map>
    </property>
  </bean>
  
  <bean id="XSLTProcessor"
    class="mx4j.tools.adaptor.http.XSLTProcessor" />
  <bean id="HttpAdaptor"
    class="mx4j.tools.adaptor.http.HttpAdaptor">
    <property name="processor" ref="XSLTProcessor"/>
    <property name="port" value="8080"/>
  </bean>
  
  <bean id="ExampleService"
    class="com.claudeduguay.jmx.demo.server.ExampleService" />

</beans>

If you like (assuming you've followed along with my setup), you can now run the server. It will register the ExampleService and run the HTTP adaptor. Don't forget to use the command-line arguments mentioned in the comments to start the Java 5.0 MBeanServer; otherwise, you'll get the default instance and the client example won't work.


Running the client code

Once you've started the server, you can run the client code shown in Listing 8 to see what happens. The code implements the JMX NotificationListener interface so that you can see things interactively. Once you have a connection, you can register the listener and trigger a few calls, both starting and stopping the service and setting and getting the attribute. In each case, you should see a notification message on the console that confirms your actions.


Listing 8. SpringJmxClient
  
package com.claudeduguay.jmx.demo.client;

import java.util.*;

import javax.management.*;
import javax.management.remote.*;

public class SpringJmxClient implements NotificationListener
{
  public void handleNotification(
    Notification notification, Object handback) 
  {
    System.out.println(
      "Notification: " + notification.getMessage());
  }
  
  public static void main(String[] args)
    throws Exception
  {
    SpringJmxClient listener = new SpringJmxClient();
    
    String address =
      "service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
    JMXServiceURL serviceURL = new JMXServiceURL(address);
    Map<String,Object> environment = null;
    JMXConnector connector =
      JMXConnectorFactory.connect(serviceURL, environment);
    MBeanServerConnection mBeanConnection =
      connector.getMBeanServerConnection();
    
    ObjectName exampleServiceName =
      ObjectName.getInstance("Services:name=ExampleService");
    mBeanConnection.addNotificationListener(
      exampleServiceName, listener, null, null);
    
    mBeanConnection.invoke(
      exampleServiceName, "startService", null, null);
    mBeanConnection.setAttribute(exampleServiceName,
      new Attribute("propertyValue", "new value"));
    System.out.println(mBeanConnection.getAttribute(
      exampleServiceName, "propertyValue"));
    mBeanConnection.invoke(
      exampleServiceName, "stopService", null, null);
  }
} 

Because the HTTP adaptor is also available, you can try using MX4J (through a browser connection to port 8080) to manage the same methods and attributes. If you leave the client code running while you do this, you'll see the notifications for those actions as well.


In conclusion

In this article, I've shown you how to extend Spring's JMX support to meet your specific application needs. In this case, I used Spring's container-based architecture and AOP framework to add notification events to JMX methods and attributes. Naturally, I've only scratched the surface of what can be done with Spring JMX. Many other extensions are possible, and both Spring and JMX are vast subjects, each worthy of further investigation. I encourage you to study the source code that comes with the article and see the Resources section below to learn more about Spring JMX and related technologies.



Downloads

DescriptionNameSizeDownload method
Source codej-Extending-Spring-JMX.jar22 KB HTTP
Source codej-Extending-Spring-JMX-src.zip3 MB HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Claude Duguay

Claude is senior J2EE architect at Capital Stream Inc. He has over 25 years of software development experience and started working with the Java platform when the first beta was released. Claude has published more than 75 articles and more than 150 book reviews, primarily focused on the Java language and XML.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development, Open source
ArticleID=97572
ArticleTitle=Extending Spring JMX support
publish-date=11012005
author1-email=claude.duguay@verizon.net
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers