JMX AttributeChangeNotifications, MXBeans, and a clever little helper class


Java Management eXtensions (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:
  1. 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.
  2. 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!
Comments:

Post a Comment:
Comments are closed for this entry.
About

nickstephen

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today