One of the cool things in the Java Platform I've always liked is the
introspection and reflection capabilities. Although Java is surely not
the first to provide this, it is quite a step up from using say,
dlopen() and dlsym() in C code for example.
These features recently help me elegantly solve a problem I had with
JMX. I have been looking at instrumenting the code that is used serve
many of the www.sun.com web pages,
in order to give us better information on how the system is performing.
The Java Management Extensions (JMX) provide a pretty straight forward
way to enable management and instrumention in your applications. But
here's the problem I was having.
The easiest way to use JMX is to use Standard MBeans. This is done
by creating an bean interface class which you then implement
in your code. The interface defines getter and setter methods
for data points you want to monitor and/or change. JMX, itself, then
uses introspection to find and use the MBean interface.
Here's an example:
public interface DispatcherMBean {
public int getRequestsProcessedCount();
public void setCacheSize(int cacheSize);
}
public class Dispatcher implements DispatcherMBean {
public int getRequestsProcessedCount() {
return statRequestsProcessedCount;
}
public void setCacheSize(int cacheSize) {
this.maxCacheSize = cacheSize;
}
/*
* ...
*/
}
Now to register the object with the MBeanServer its only a few
simple method calls:
ObjectName dispatcherName = null;
dispatcherName = new ObjectName("Domain:name=Dispatcher");
server.registerMBean(this, dispatcherName);
This all works great. Very straightforward. Very simple. The only
problem is that although JMX provides the ability to tie descriptions
to each attribute, the Standard MBean used this way doesn't provide
the ability to create them. Having descriptions available would
definitely be helpful when working in a Management Console trying
to figure out what each attribute means.
So this is the problem I was trying to solve in the first place. How
to easily maintain descriptions for the JMX attributes I was creating.
First, I looked into the other JMX MBeans. The Dynamic MBean, for
example, provides alot more flexibility, including creating
descriptions, but that flexibility comes at a cost. There is alot more
code involved in instrumenting your code with Dynamic MBeans. You have
maintain a lot of different structures like MBeanAttributeInfo,
MBeanOperationInfo, MBeanConstructorInfo, etc.. You also have
more generalized methods that you implement like getAttribute()
and setAttribute(). The code becomes quite long, an example
that comes with JMX is SimpleDynamic.java
So I decided that was out.
As I was trying out different JMX implementations, I noticed in the
MX4J documentation that they provide
an extension in MX4J for maintaining
descriptions with Standard MBeans. The documentation says that
this extension is "Totally transparent with respect to MBeans
portability across JMX implementations." owever, the solution
relies on a couple of MX4J-specific classes. Without them, your
code would not compile, so in essence you are tied to MX4J. I strive
to stay free from specific implementations so I decide this extension
was out.
So as I searched around a little bit more, I found that there is an
alternate way to create a Standard MBean which provides some
customization hooks including the ability to maintain descriptions for
attributes. Its done by using the base class
StandardMBean. And the specific method that I was interested
in was:
public String getDescription(MBeanAttributeInfo info);
Ok, so I had to first restructure my code to extend
StandardMBean, which has its own problems because much of our
code already extends other classes. But I managed to come up with a
peer class framework. The peer class is given access to protected
fields for management, this ends up being a bit of a plus to have the
management functions separated out.
Now the only problem is how to easily manage descriptions and how to
implement getDescription(). One obvious thought is to create
a Map of some sort or a Properties file
ResourceBundle and initialize it with all the attribute
names and descriptions. I started going down that route but it just
seemed clunky.
As I looked back, and thought about how JMX uses reflection, I
realized I could use reflection myself to easily accomplish this.
What I figured I could do is come up with some sort of naming scheme
to some internal String fields which I could programmatically
search for and get the values of. And that's exactly what I did.
All that I do is create public static String fields that are
named the same as the attributes with the additional suffix of
Desc. And then I created a getDescription() method
that takes the name of the attribute passed in, tacks on "Desc" to the
end, and then uses Class.getField() to search for it.
My original example, modified this way looks like this:
public interface DispatcherMBean {
public int getRequestsProcessedCount();
public void setCacheSize(int cacheSize);
}
public class DispatcherMBeanImpl extends StandardMBean
implements DispatcherMBean {
public DispatchMBeanImpl(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public static String =
"Contains the total number of requests processed";
public int getRequestsProcessedCount() {
return dispatcher.statRequestsProcessedCount;
}
public static String =
"The cache size, in megabytes.";
public void setCacheSize(int cacheSize) {
dispatcher.maxCacheSize = cacheSize;
public String getDescription(MBeanAttributeInfo info) {
try {
Field field = getClass().getField(info.getName() + "Desc");
return (String) field.get(this);
}
catch (Exception e) {
e.printStackTrace();
return info.getDescription();
}
}
/*
* ...
*/
}
And to register the MBean, only a couple of simple lines still:
DispatchMBeanImpl mbean = new DispatcherMBeanImpl(dispatcher);
ObjectName dispatcherName = null;
dispatcherName = new ObjectName("Domain:name=Dispatcher");
server.registerMBean(mbean, dispatcherName);
And that's it. Pretty simple.
|