Saturday, February 16, 2008

Publishing JMX Notifications with Spring

The Spring 2.5 Reference provides excellent coverage of how to publish JMX notifications with Spring. Note that these JMX Notifications can be published locally or remotely. In this blog entry, I will demonstrate how to publish a JMX Notification via Spring for remote listeners. Another outstanding resource on JMX remote features is the Sun Java Tutorial entry Creating a Custom JMX Client.

The first code shown here is the XML code that configures our Spring container.

spring-jmx-notif-server.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="someObject"
class="dustin.server.SomeJavaObject" />

<bean class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="dustin.example:name=jmx,type=someJavaObject"
value-ref="someObject" />
</map>
</property>
</bean>

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1099"/>
</bean>

<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean"
depends-on="registry">
<property name="objectName" value="connector:name=rmi"/>
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>

</beans>


Note that nothing specific to publishing JMX Notifications is included in this XML configuration, though there are entries to handle remote JMX. The next source code listing shows a pretty normal Java class, but it does feature a Spring-specific interface (NotificationPublisherAware) that it implements a single method (setNotificationPublisher) for.

SomeJavaObject.java

package dustin.server;

import javax.management.Notification;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

/**
* Nothing in this class makes it explicitly a JMX MBean. Instead, Spring will
* expose this as an MBean. The Spring-specific NotificationPublisherAware
* interface is implemented by this class, which means that Spring will also
* allow this bean-turned-MBean to easily publish JMX notifications.
*
* @author Dustin
*/
public class SomeJavaObject implements NotificationPublisherAware
{
private int notificationIndex = 0;
private NotificationPublisher notificationPublisher;
private String someValue = "Nada";

public SomeJavaObject()
{
// empty default constructor
}

public String getSomeValue()
{
return this.someValue;
}

public void setSomeValue(final String aSomeValue)
{
notificationPublisher.sendNotification(
buildNotification(this.someValue, aSomeValue) );
this.someValue = aSomeValue;
}

/**
* Generate a Notification that will ultimately be published to interested
* listeners.
*
* @param aOldValue Value prior to setting of new value.
* @param aNewValue Value after setting of new value.
* @return Generated JMX Notification.
*/
private Notification buildNotification(
final String aOldValue,
final String aNewValue )
{
final String notificationType = "dustin.jmx.spring.notification.example";
final String message = "Converting " + aOldValue + " to " + aNewValue;
final Notification notification =
new Notification( notificationType,
this,
notificationIndex++,
System.currentTimeMillis(),
message );
notification.setUserData("Blog Example #" + notificationIndex );
return notification;
}

/**
* This is the only method required to fully implement the
* NotificationPublisherAware interface. This method allows Spring to
* inject a NotificationPublisher into me.
*
* @param aPublisher The NotificationPublisher that Spring injects into me.
*/
public void setNotificationPublisher(NotificationPublisher aPublisher)
{
this.notificationPublisher = aPublisher;
}
}


The above class is not an MBean by itself. Rather, Spring exposes it as an MBean because of our use of the MBeanExporter in the XML configuration. However, there were some JMX-specific classes in this class to enable Notifications. Also, as mentioned above, the Spring-specific NotificationPublisherAware interface is also explicitly implemented by this class.

We need a main executable Java class to bootstrap the Spring container and to allow us the opportunity to interact and set someValue so that the JMX Notifications will occur. The source code for that class is shown next.

JmxSpringServerMain.java

package dustin.server;

import java.io.Console;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
* Main executable designed to instantiate a Spring context and avoid exiting
* immediately so that a JMX client can listen to notifications sent from a
* Spring-exposed MBean Notification Publisher.
*
* This code sample requires Java SE 6 because it uses System.console() and
* String.isEmpty().
*
* @author Dustin
*/
public class JmxSpringServerMain
{
/**
* Main execution for JMX Spring Notification Publication example (server).
*
* @param args the command line arguments
*/
public static void main(String[] aCommandLineArgs)
{
ConfigurableApplicationContext context =
new FileSystemXmlApplicationContext(
"C:\\NetBeansProjects\\JMXSpringNotificationServer\\spring-jmx-notif-server.xml");
SomeJavaObject obj = (SomeJavaObject) context.getBean("someObject");
final Console systemConsole = System.console();
if ( systemConsole != null )
{
final String terminateLoop = "exit";
String choice = null;
do
{
systemConsole.printf("Enter a choice: ");
choice = systemConsole.readLine();
if ( choice != null && !choice.isEmpty() )
{
obj.setSomeValue(choice);
}
}
while ( choice != null
&& !choice.isEmpty()
&& !choice.equals(terminateLoop) );
}
else
{
System.err.println(
"Please run this in an environment with a system console.");
}
context.close();
}
}


The above class can be run to use the Spring container to expose SomeJavaObject as an MBean capable of sending JMX Notifications to local or remote clients. Note that Java SE 6 is required to run this example because of use of features specific to Java SE 6. When this main Java class is executed, the output will look something like the following screen snapshot (click on it to see larger version). I have entered some arbitrary values to demonstrate the JMX Notifications will be sent when we attach a JMX client later.



We can run JConsole to see if the JMX Notifications are being published. The following screen snapshot (click on it to see larger version) shows how JConsole appears when run alongside the application executed above. Note that I used Java SE 6's JConsole and that I had JConsole already running when executing the main Java application and that I had clicked on its "Subscribe" button in the MBeans Notifications section.



There are several interesting observations from this JConsole output. For one, JConsole nicely displays the timestamps I provided in the Notification as a long timestamp. Another interesting observation here is that the UserData column displays exactly the String I passed the Notification.setUserData(Object) method in the code. This only worked because I passed a String to this method that expects an Object. Had I not passed a String, I would have needed to pass a class that had a toString() method overridden appropriately and was available to a remote JMX client to have this be a sensible value. Using a String was the easy way out for this example.

You may have noticed that I did not need to specify the "notificationPublisher" injected into the class that implements Spring's NotificationPublisherAware interface. Spring apparently does this automatically without any need to explicitly specify/configure this injection. In fact, I lost more time than I care to admit trying to specify this Notification Publisher in the Spring XML configuration file because this is shown in the second edition of Spring in Action (page 483). This book is generally a nice introduction to Spring and has been very beneficial for me, but something must have changed in Spring since the writing of this section. Anyway, it seems that you should NOT explicitly configure the injection of ModelMBeanNotificationPublisher despite what is shown on page 483. By the way, if you omit that one XML snippet from this book's coverage of JMX Notifications with Spring, the remainder of the example seems to accurately describe JMX Notification Publishing via Spring.

The Spring documentation warns repeatedly that any natural JMX MBeans (objects that are MBeans before Spring exposes them as such) should NOT use Spring's JMX publication support and should instead use JMX Notification APIs directly.

4 comments:

Jugash said...

thanks for the bit on mistake in 'Spring in action' book. Saved me time.

@DustinMarx said...

Jugash,

Thanks for letting me know this was helpful. This issue definitely cost me some time and I hoped that blogging on it would help someone else.

Thanks again for taking the time to let me know it was helpful.

Dustin

lisdey89 said...

I have a problem doing this example, i almost copy your code into my example but it didn't work. It give me a java.lang.NullPointerException, I think that error is because for some reason Spring is not injecting the NotificationPublisher instance into me MBean, but I don't know how can I fix this. Please can you help me?

Binod Suman said...

Hi,

Thanks for sharing such a good and easy tutorial on JMX, I just copied and paste your code it got working without any problem. But for simplicity this tutorial could be made with RMI setup.

Thanks again.

Binod Suman
Bangalore, India