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.
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 anMBeanInfoinstance. 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.
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.
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.
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).
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.
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.
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
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.
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);
}
}
}
|
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.
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.
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>
|
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.
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 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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code | j-Extending-Spring-JMX.jar | 22 KB | HTTP |
| Source code | j-Extending-Spring-JMX-src.zip | 3 MB | HTTP |
Information about download methods
Learn
- AOP@Work: Performance monitoring with AspectJ (Ron Bodkin, developerWorks, September 2005): Combine AspectJ and JMX to create a flexible, aspect-oriented monitoring infrastructure.
-
Secrets of lightweight development success: How to lighten up your containers (Bruce Tate, developerWorks, June 2005): Explains the downsides of heavyweight architectures like EJB and the comparative
advantages of lightweight containers (series).
-
Inversion of Control Containers and the Dependency Injection pattern
(Martin Fowler, January 2004): A closer look at the dependency injection
pattern underlying lightweight containers such as Spring.
- JMX home page: Learn more about the Java Management Extensions directly from Sun Microsystems.
- JMX Best Practices: Best practices for modeling using the JMX API.
- AOP Alliance:
Works to facilitate and standardize the use of AOP in Java-based middleware environments such as J2EE.
- The Java technology
zone: Find articles about every aspect of Java programming.
Get products and technologies
- The Spring framework:
Download the Spring framework and documentation (now in version 1.2).
- MX4J:
The leading open source implementation of the JMX standard.
- MBeanInspector for WebSphere: The JMX-based administration program for WebSphere 5, from
alphaWorks.
Discuss
- developerWorks blogs:
Get involved in the developerWorks community.

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)





