JMX AttributeChangeNotifications, MXBeans, and a clever little helper class
Java
Management e
Xtensions (
JMX)
provide a means for management interfaces to be exposed as MBeans
allowing remote monitoring and management of your application.
MBeans are bean-like objects that can contain attributes and
operations, and can generate notifications to interested listeners. One
such predefined type of JMX Notification is the
AttributeChangeNotification
which can be optionally emitted when the value of an attribute has
changed. These notifications contain the old value and the new value of
the attribute.
MXBeans
are a special type of MBean that allows the developer to use complex
types for MBean attributes and operation parameters, but only exposes a
predefined set of types to the outside world - the ones defined by
javax.management.openmbean.
In this way, you can be sure that your MBean will be usable by any
client, including remote clients, without any requirement that the
client have access to
model-specific classes representing the
types of your MBeans.
MXBeans
translate between the complex types in the code and the predefined set
of open types.
This blog article shows how to write a simple helper class for
generating attribute change notifications with the correct contents for
the old and new attribute values, even for MXBeans. It's based upon a
similar
helper-class idea another good idea from
Eamonn's blog.
Generating a JMX AttributeChange notification when the value of an
attribute changes is typically very simple, and is described in
JMX
essentials.
Basically, you need to do two things:
- Your MBean implementation class needs to implement the NotificationBroadcaster
interface, this is often done by having your MBean implementation class
extend NotificationBroadcasterSupport,
or using an instance of this class in a delegation model.
- Your code needs to call "sendNotification" at the right times to
send notifications to interested listeners.
A simple example from the
Java
JMX tutorial is copied below:
/*
* Hello.java - MBean implementation for the Hello MBean. This
class must
* implement all the Java methods declared in the HelloMBean
interface,
* with the appropriate behavior for each one.
*/
package com.example;
import javax.management.*;
public class Hello
extends NotificationBroadcasterSupport implements
HelloMBean {
public void sayHello() {
System.out.println("hello, world");
}
public int add(int x, int y) {
return x + y;
}
/* Getter for the Name attribute. The pattern shown
here is frequent: the
getter returns a private field
representing the attribute value. In our
case, the attribute value never
changes, but for other attributes it
might change as the application
runs. Consider an attribute representing
statistics such as uptime or
memory usage, for example. Being read-only
just means that it can't be
changed through the management interface. */
public String getName() {
return this.name;
}
/* Getter for the CacheSize attribute. The pattern
shown here is
frequent: the getter returns a
private field representing the
attribute value, and the setter
changes that field. */
public int getCacheSize() {
return this.cacheSize;
}
/* Setter for the CacheSize attribute. To avoid
problems with
stale values in multithreaded
situations, it is a good idea
for setters to be synchronized. */
public synchronized void setCacheSize(int size) {
int oldSize = this.cacheSize;
this.cacheSize = size;
/* In a real application, changing the attribute
would
typically have effects beyond just
modifying the cacheSize
field. For example, resizing the
cache might mean
discarding entries or allocating new
ones. The logic for
these effects would be here. */
System.out.println("Cache size now " +
this.cacheSize);
/* Construct a notification that describes the
change. The
"source" of a notification is the
ObjectName of the MBean
that emitted it. But an MBean can put a
reference to
itself ("this") in the source, and the
MBean server will
replace this with the ObjectName before
sending the
notification on to its clients.
For good measure, we maintain a
sequence number for each
notification emitted by this MBean.
The oldValue and newValue parameters to
the constructor are
of type Object, so we are relying on
Tiger's autoboxing
here. */
Notification n =
new
AttributeChangeNotification(this,
sequenceNumber++,
System.currentTimeMillis(),
"CacheSize
changed",
"CacheSize",
"int",
oldSize,
this.cacheSize);
/* Now send the notification using the
sendNotification method
inherited from the parent class
NotificationBroadcasterSupport. */
sendNotification(n);
}
@Override
public MBeanNotificationInfo[] getNotificationInfo()
{
String[] types = new String[] {
AttributeChangeNotification.ATTRIBUTE_CHANGE
};
String name =
AttributeChangeNotification.class.getName();
String description = "An attribute of this MBean has
changed";
MBeanNotificationInfo info =
new MBeanNotificationInfo(types,
name, description);
return new MBeanNotificationInfo[] {info};
}
private final String name = "Reginald";
private int cacheSize = DEFAULT_CACHE_SIZE;
private static final int DEFAULT_CACHE_SIZE = 200;
private long sequenceNumber = 1;
}
|
A
simple Hello MBean that emits notifications, copied from the JMX
Tutorial
|
Note that the MBean interface does not need to implement
NotificationBroadcaster, it's just the implementing class that does so.
So why the need for a helper class for sending
AttributeChangeNotifications?
Well, if the MBean is an MXBean, the above code may send out an
AttributeChangeNotification with a wrong types for the old and new
values - if the type of the attribute is complex, the complex type will
be sent out instead of the open type as mapped by the MXBean. The
attribute in question is no longer exposing the complex types to the
outside world, and these types are not understood by JMX tools such as
JConsole.
This problem can be shown by adapting the previous example in the
tutorial, the
MXBean
example, so that it also emits
notifications:
/**
* QueueSampler.java - MXBean implementation for the QueueSampler
MXBean.
* This class must implement all the Java methods declared in the
* QueueSamplerMXBean interface, with the appropriate behavior for
each one.
*/
package com.example;
import java.util.Date;
import java.util.Queue;
import javax.management.AttributeChangeNotification;
import javax.management.MBeanNotificationInfo;
import javax.management.NotificationBroadcasterSupport;
import javax.management.StandardEmitterMBean;
public class QueueSampler extends StandardEmitterMBean implements
QueueSamplerMXBean {
private Queue<String> queue;
private long seqNo = 0;
public QueueSampler(Queue<String> queue) {
super(QueueSamplerMXBean.class, true, new
NotificationBroadcasterSupport());
this.queue = queue;
}
public QueueSample getQueueSample() {
synchronized (queue) {
return new QueueSample(new Date(), queue.size(), queue.peek());
}
}
public MBeanNotificationInfo[] getNotificationInfo()
{
return new
MBeanNotificationInfo[] {
new
MBeanNotificationInfo(
new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
"javax.management.AttributeChangeNotification",
"Attribute change notification")
};
}
public void clearQueue() {
synchronized (queue) {
QueueSample oldValue = getQueueSample();
queue.clear();
QueueSample newValue = getQueueSample();
AttributeChangeNotification notif = new AttributeChangeNotification(
this,
seqNo++,
System.currentTimeMillis(),
"attribute change",
"QueueSample",
"com.example.QueueSample",
oldValue,
newValue);
sendNotification(notif);
}
}
}
|
The
QueueSampler example updated to emit AttributeChangeNotifications
|
If you follow the same steps as are described in the
lesson
on notifications, and connect up JConsole to listen to
Notifications emitted by your MBean, you may be puzzled to see that no
notifications are being emitted when clearQueue() is being called!
What's actually happening is that the notification
is being emitted, but it contains a
complex type that JConsole doesn't know about, so it cannot deserialize
the Notification, and it's silently dropped.
You can see that this is the problem by changing 'oldValue' and
'newValue' in the above construction of the notification to a couple of
Strings such as "fu" and "bar". Then, when you call tthe 'clearQueue'
operation, you'll see the Notification. Unfortunately, JConsole won't
display the 'old' and 'new' values, but you'll see that the
notification was emitted.
Manual computation of the correct MXBean mappings for complex attribute
values
is a complex and unnecessary way of doing things here. Instead, one can
use JMX to do the work for you. If your MXBean subclasses
StandardMBean,
instead of referring directly to the value of the attribute, you can
read the attribute's MXBean-mapped value by using the
StandardMBean's
getAttribute()
call. This technique works for any attribute types.
Thus your method's code might be updated to look like this:
public void clearQueue() {
synchronized (queue) {
Object oldValue = this.getAttribute("QueueSample");
queue.clear();
Object newValue = this.getAttribute("QueueSample");
AttributeChangeNotification notif = new AttributeChangeNotification(
this,
seqNo++,
System.currentTimeMillis(),
"attribute change",
"QueueSample",
newValue.getClass().getName(),
oldValue,
newValue);
sendNotification(notif);
}
}
|
An
updated implementation sending correct MXBean types in the
AttributeChangeNotification
|
By applying the same ideas as found in the small
helper
class for performance measurement, it's possible to make an elegant
helper class for AttributeChangeNotifications, so that the MBean's
implementation doesn't need to worry about notification infos, sequence
numbers, old or new values, and can simply become this:
public MBeanNotificationInfo[] getNotificationInfo()
{
return
AttrChangeHelper.getNotificationInfo();
}
public void clearQueue() {
synchronized (queue) {
AttrChangeHelper.Change change = helper.newChange("QueueSample");
queue.clear();
change.end();
}
}
|
An
updated implementation that uses the helper class below
|
... The Change class instance here is first retrieving the old value of
the attribute from the MBean (when created), then the new value (when
'end' is called) and then, if there is a difference between the two, it
is calling back to the MBean's 'sendNotification' method to emit the
notification.
Here is that helper class that can be reused for any
AttributeChangeNotification.
package
com.example;
import javax.management.AttributeChangeNotification;
import javax.management.AttributeNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanNotificationInfo;
import javax.management.ReflectionException;
import javax.management.StandardEmitterMBean;
/**
* A helper class used to prepare, then emit, a new
AttributeChangeNotification
* This class deals with any mxbean translations
*/
public class AttrChangeHelper {
final StandardEmitterMBean mbean;
long seqNo = 0;
/**
* Creates a new instance of AttrChangeHelper
*/
public AttrChangeHelper(
StandardEmitterMBean mbean) {
this.mbean = mbean;
}
public static MBeanNotificationInfo[]
getNotificationInfo() {
return new
MBeanNotificationInfo[] {
new
MBeanNotificationInfo(
new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
"javax.management.AttributeChangeNotification",
"Attribute change notification")
};
}
public synchronized Change newChange(String
attributeName) {
try {
//
for mxbeans - this will translate into the
//
open type available on our MBean. For normal
//
MBeans, this will just get the normal type.
return new Change(attributeName, mbean.getAttribute(attributeName));
} catch
(AttributeNotFoundException ex) {
throw new IllegalArgumentException("Attribute not found :
"+
attributeName, ex);
} catch (ReflectionException
ex) {
throw new IllegalArgumentException("Attribute not found :
"+
attributeName, ex);
} catch (MBeanException ex) {
throw new IllegalArgumentException("Attribute not found :
"+
attributeName, ex);
}
}
public class Change {
String attributeName;
Object oldValue;
private Change(String
attributeName, Object oldValue) {
this.attributeName = attributeName;
this.oldValue = oldValue;
}
public synchronized void
end() {
Object newValue = null;
try {
// for mxbeans - this will translate into the
// open type available on our MBean. For normal
// MBeans, this will just get the normal type.
newValue = mbean.getAttribute(attributeName);
}
catch (AttributeNotFoundException ex) {
throw new IllegalArgumentException("Attribute not found :
"+attributeName, ex);
}
catch (ReflectionException ex) {
throw new IllegalArgumentException("Attribute not found :
"+attributeName, ex);
}
catch (MBeanException ex) {
throw new IllegalArgumentException("Attribute not found :
"+attributeName, ex);
}
AttributeChangeNotification notif = new AttributeChangeNotification(
mbean,
seqNo++,
System.currentTimeMillis(),
"Attribute \"" + attributeName + "\" changed",
attributeName,
newValue.getClass().getName(),
oldValue,
newValue);
mbean.sendNotification(notif);
}
}
}
|
The AttributeChangeNotification helper
class
|
The whole example, including netbeans project, can be
downloaded
here.
As stated before, the helper class idea is directly inspired from
Eamonn's
article. Thanks again Eamonn!
( May 22 2007, 11:54:27 AM CEST )
Permalink