Search |
||
Adding information to a Standard MBean interface using annotationsPosted by emcmanus on July 25, 2005 at 9:52 AM PDT
With a Standard MBean, you define the management interface of the MBean using a Java interface. Getters and setters in the interface define attributes, and other methods define operations. But the only information extracted out of the interface is the names and types of the attributes and operations, and just the types of the operation parameters. Although the JMX API allows for textual descriptions to be associated with attributes, operations, and parameters, when you use a Standard MBean these descriptions have meaningless default values. Parameters also have default names like p1, p2, etc. Here's how you can use subclassing and annotations to overcome these limitations. Suppose you have a Standard MBean that looks like this:
public interface CacheControllerMBean {
/** Drop the n oldest entries whose size matches the given constraints. */
public int dropOldest(int n, int minSize, int maxSize);
}
You implement the MBean in the usual way, register it in your MBean Server...
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=CacheController");
CacheController mbean = new CacheController(...);
mbs.registerMBean(mbean, name);
...and then fire up jconsole to see what it looks like.
Well, this is good, the operation is there, but the information about it could be improved. The useful parameter names we gave have been replaced with p1, p2, p3, and when we let the mouse hover over the operation button, we get a tooltip with the default text "Operation exposed for management". You wouldn't have expected the MBean Server to have been able
to guess a better description than that, but you might be
surprised that the parameter names that were in your original
With a bit more work you can put more information into your MBeans to produce a better user interface. The technique I'm suggesting is to add annotations to your Standard MBean that will allow you to specify descriptions for operations (and attributes and MBeans) and to give parameter names in a way that is accessible through reflection. The idea is that your MBean interface would look like this:
public interface CacheControllerMBean {
@Description("Drop the n oldest entries whose size matches the given constraints")
public int dropOldest(@PName("n") int n,
@PName("minSize") int minSize,
@PName("maxSize") int maxSize);
}
The Here's what the
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface PName {
String value();
}
When you're defining an annotation, you need to check out the
meta-annotations in
dropOldest
@Description(value="Drop the n oldest entries whose size matches the given constraints")
int dropOldest(@PName(value="n")
int n,
@PName(value="minSize")
int minSize,
@PName(value="maxSize")
int maxSize)
You probably don't want this redundant information, although it might be useful to make sure the annotations are in fact present and have the right values.
By the way, it's important that the name of the method in the
The
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({CONSTRUCTOR, METHOD, PARAMETER, TYPE})
public @interface Description {
String value();
}
This should be fairly clear based on the discussion of the
Now that you have these annotations, how do you arrange for
them to be taken into account? The idea is to create a subclass
of
public class AnnotatedStandardMBean extends StandardMBean {
/** Instance where the MBean interface is implemented by another object. */
public <T> AnnotatedStandardMBean(T impl, Class<T> mbeanInterface)
throws NotCompliantMBeanException {
super(impl, mbeanInterface);
}
/** Instance where the MBean interface is implemented by this object. */
protected AnnotatedStandardMBean(Class<?> mbeanInterface)
throws NotCompliantMBeanException {
super(mbeanInterface);
}
@Override
protected String getDescription(MBeanOperationInfo op) {
...
}
...other overrides...
}
And the code that registers your MBean will now look like this:
ObjectName name = new ObjectName("com.example:type=CacheController");
CacheController cc = new CacheController(...);
Object mbean = new AnnotatedStandardMBean(cc, CacheControllerMBean.class);
mbs.registerMBean(mbean, name);
By the way, an advantage of this approach is that you no longer
have to follow the rigid naming convention where class
Let's consider the
protected String getDescription(MBeanOperationInfo op) {...}
You're going to want to find the method corresponding to this
operation, and if it has a Finding the method corresponding to an
Unfortunately, there is no standard method
to convert back from
static Class<?> classForName(String name, ClassLoader loader)
throws ClassNotFoundException {
Class<?> c = primitiveClasses.get(name);
if (c == null)
c = Class.forName(name, false, loader);
return c;
}
private static final Map<String, Class<?>> primitiveClasses =
new HashMap<String, Class<?>>();
static {
Class<?>[] prims = {
byte.class, short.class, int.class, long.class,
float.class, double.class, char.class, boolean.class,
};
for (Class<?> c : prims)
primitiveClasses.put(c.getName(), c);
}
Though it's probably not really necessary, I've got into the
habit of writing Now that you have the
private static Method methodFor(Class<?> mbeanInterface,
MBeanOperationInfo op) {
final MBeanParameterInfo[] params = op.getSignature();
final String[] paramTypes = new String[params.length];
for (int i = 0; i < params.length; i++)
paramTypes[i] = params[i].getType();
return findMethod(mbeanInterface, op.getName(), paramTypes);
}
private static Method findMethod(Class<?> mbeanInterface, String name,
String... paramTypes) {
try {
final ClassLoader loader = mbeanInterface.getClassLoader();
final Class<?>[] paramClasses = new Class<?>[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++)
paramClasses[i] = classForName(paramTypes[i], loader);
return mbeanInterface.getMethod(name, paramClasses);
} catch (RuntimeException e) {
// avoid accidentally catching unexpected runtime exceptions
throw e;
} catch (Exception e) {
return null;
}
}
That gives you enough to be able to write the
@Override
protected String getDescription(MBeanOperationInfo op) {
String descr = op.getDescription();
Method m = methodFor(getMBeanInterface(), op);
if (m != null) {
Description d = m.getAnnotation(Description.class);
if (d != null)
descr = d.value();
}
return descr;
}
Getting the parameter names out of the
for (Annotation a : m.getParameterAnnotations()[paramNo]) {
if (a instanceof PName)
return (PName) a;
}
You can generalize to a method that works for any annotation
rather than just
static <A extends Annotation> A getParameterAnnotation(Method m,
int paramNo,
Class<A> annot) {
for (Annotation a : m.getParameterAnnotations()[paramNo]) {
if (annot.isInstance(a))
return annot.cast(a);
}
return null;
}
The fiddling with generics is to say that, if the
Now you have everything you need for the override that extracts
a parameter name from the
@Override
protected String getParameterName(MBeanOperationInfo op,
MBeanParameterInfo param,
int paramNo) {
String name = param.getName();
Method m = methodFor(getMBeanInterface(), op);
if (m != null) {
PName pname = getParameterAnnotation(m, paramNo, PName.class);
if (pname != null)
name = pname.value();
}
return name;
}
Using the
The tooltip contains useful text, and the parameters have real names. How gratifying! There are still some other things you're likely to want in the
»
Related Topics >>
Open JDK Comments
Comments are listed in date ascending order (oldest first)
|
||
|
|