One curiosity about Model MBeans is that attributes also appear as operations. Is there any way to avoid that?

We encounter this question occasionally, most recently in the JMX forum on SDN. As the contributor there notes, this is tracked as RFE 6339571, but won't be implemented until Java SE 7. What can you do in the mean time?

In order to define an attribute, say Foo, as being the result of calling the getBar method, Model MBeans require you to define a ModelMBeanAttributeInfo for Foo with the getMethod set to getBar. That seems reasonable so far. But they also require you to define a ModelMBeanOperationInfo for getBar. So if you connect with JConsole, say, then not only will you see the Foo attribute, you will also see the getBar operation. There are reasons for this, which are alluded to in RFE 6339571, but it's still annoying.

The getBar operation needs to be in the MBeanInfo so that the Model MBean can find it when you access the Foo attribute. But it doesn't need to be in the MBeanInfo that JConsole sees. Is there some way we could arrange for the MBeanInfo to be different in these two cases?

The answer is yes, via a hack. We can tweak the serialization of this MBeanInfo so that operations like getBar are removed when the MBeanInfo is being sent to a remote client such as JConsole. This will have no effect on the local MBeanInfo that the Model MBean itself sees, so the Foo attribute will continue to work.

We'll define a subclass of ModelMBeanInfoSupport called NoGetterMBeanInfo, and add a writeReplace method to this class. The writeReplace method can return a different object to be serialized in the place of the original one. Instead of serializing a NoGetterMBeanInfo, which would require a client such as JConsole to know that class, we can serialize a ModelMBeanInfoSupport. Since that's a standard class, every client must know it. And we can arrange for this new ModelMBeanInfoSupport not to contain any getter methods like getBar.

We identify getter methods by their Descriptor: if the Descriptor contains a role field with the value "getter", then we assume it's a getter. Likewise if the value is "setter", we'll assume it's a setter and also delete it. This is not a foolproof test: if the value is "operation" then it can still be used as a getter, but we'll assume that you can either change your code so that the field has the right value, or change NoGetterMBeanInfo to use a different test.

Here then is the code for NoGetterMBeanInfo:

 import java.util.ArrayList; import java.util.List; import javax.management.Descriptor; import javax.management.MBeanException; import javax.management.MBeanOperationInfo; import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanConstructorInfo; import javax.management.modelmbean.ModelMBeanInfo; import javax.management.modelmbean.ModelMBeanInfoSupport; import javax.management.modelmbean.ModelMBeanNotificationInfo; import javax.management.modelmbean.ModelMBeanOperationInfo; public class NoGetterMBeanInfo extends ModelMBeanInfoSupport { public NoGetterMBeanInfo(ModelMBeanInfo mmbi) { super(mmbi); } @Override public NoGetterMBeanInfo clone() { return new NoGetterMBeanInfo(this); } private Object writeReplace() { List ops = new ArrayList(); for (MBeanOperationInfo mboi : this.getOperations()) { ModelMBeanOperationInfo mmboi = (ModelMBeanOperationInfo) mboi; Descriptor d = mmboi.getDescriptor(); String role = (String) d.getFieldValue("role"); if (!"getter".equalsIgnoreCase(role) && !"setter".equalsIgnoreCase(role)) ops.add(mmboi); } ModelMBeanOperationInfo[] mbois = new ModelMBeanOperationInfo[ops.size()]; ops.toArray(mbois); Descriptor mbeanDescriptor; try { mbeanDescriptor = this.getMBeanDescriptor(); } catch (MBeanException e) { throw new RuntimeException(e); } return new ModelMBeanInfoSupport( this.getClassName(), this.getDescription(), (ModelMBeanAttributeInfo[]) this.getAttributes(), (ModelMBeanConstructorInfo[]) this.getConstructors(), mbois, (ModelMBeanNotificationInfo[]) this.getNotifications(), mbeanDescriptor); } } 

To use it, change code where you do something like this...

 ModelMBean mbean = new RequiredModelMBean(myModelMBeanInfo); 

...into this...

 ModelMBean mbean = new RequiredModelMBean(new NoGetterMBeanInfo(myModelMBeanInfo)); 

Of course this hack will only work if you are using a connector that is based on Java object serialization, such as the RMI connector that is part of the Java platform. If you are using a SOAP-based connector, say, then you will need to look at how to achieve the same result in that context. (One possibility is to insert an MBeanServerForwarder that intercepts a remote getMBeanInfo operation and rewrites the MBeanInfo as above.)

The forum question asked about how to do this in the context of Spring; since Spring uses Model MBeans the question arises frequently. I was able to plug in the NoGetterMBeanInfo by using a custom MBeanExporter like this:

 import javax.management.JMException; import javax.management.ObjectName; import javax.management.modelmbean.ModelMBean; import javax.management.modelmbean.ModelMBeanInfo; import org.springframework.jmx.export.MBeanExporter; public class NoGetterExporter extends MBeanExporter { public NoGetterExporter() { } @Override protected void doRegister(Object mbean, ObjectName objectName) throws JMException { if (mbean instanceof ModelMBean) { ModelMBean mmb = (ModelMBean) mbean; ModelMBeanInfo mmbi = (ModelMBeanInfo) mmb.getMBeanInfo(); mmb.setModelMBeanInfo(new NoGetterMBeanInfo(mmbi)); } super.doRegister(mbean, objectName); } } 

You probably already have an MBeanExporter in your Spring configuration file, with lines looking something like this:

 <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> ... </bean> 

You should just be able to change the MBeanExporter class name to the fully-qualified name of NoGetterExporter.

NoGetterMBeanInfo is a hack, but I hope it's a useful one!