javax.management.StandardMBean: When and Why.

Q: When is a Standard MBean not a Standard MBean?
A: When it's a StandardMBean.

A few month ago, we have added an Advanced JMX Example to JDK 6. This example builds up on the JMX Best Practices and discusses some usual JMX patterns and pitfalls to avoid when designing a Management Interface with JMX.

In this article I will give a few additional tips concerning the StandardMBean class, and in particular, when and why it becomes interesting to use it.

Standard MBean

Standard MBeans are the simplest type of MBeans. They're described in the JMX tutorial trail here.

Standard MBeans must have a management interface that follows strict naming conventions: to be a compliant MBean, a standard MBean implementation class named <package-name>.Y must implement an interface named <package-name>.Y MBean - or must extend a class which does follow these conventions.

---------------------------------------------------------------------------

// Interface is named after implementation name. It must be in the same 
// package.
package <package-name>;
public interface YMBean {...}

---------------------------------------------------------------------------

package <package-name>;
public class Y implements YMBean {...}

public static void main(String[] args) ... {
   MBeanServer server = ...;
   ObjectName  name   = ...;
   Y mbean = new Y(...);
   // mbean is a standard MBean.
   server.registerMBean(mbean,name);
   ...
}

---------------------------------------------------------------------------

Sometimes however, you'd like to be able to put interfaces and implementations in different packages, so that you could deploy only the interfaces on the client side. Interfaces are interesting to have on the client side because they allow you to create MBean proxies, which can simplify the client code. In the general case however, you don't need to have also the implementation classes on the client side, and it might even not be desirable to deploy those on the client end.

For such use cases, being forced to put the MBean interface YMBean in the same package than the MBean implementation - Y may become quite constraining.

Getting rid of the constraining name conventions

To get rid of these naming convention constraints, you have two main choices:

Using MXBeans

If you're running on the greatest JDK release ever aka JDK 6 ;-), you can turn your MBean into an MXBean. For that, you only need to end your interface name with MXBean. It does no longer need to remain in the same package than the class that implements it.

---------------------------------------------------------------------------

// Interface can be in any package.
package <interface-package-name>;
// Must end with MXBean - or must use @MXBean annotations.
// Y doesn't need to be related to the implementation class
public interface YMXBean {...}

---------------------------------------------------------------------------

package <package-name>;
import <interface-package-name>.YMXBean;
public class YImpl implements YMXBean {...}

public static void main(String[] args) ... {
   final MBeanServer server = ...;
   final ObjectName  name   = ...;
   final YImpl mbean = new YImpl(...);

   // mbean is an MXBean.
   server.registerMBean(mbean,name);
}

---------------------------------------------------------------------------

There are however some cases where using an MXBean is not feasible - for instance, if your MBean uses types which are not supported by the MXBean framework (for instance some methods use Object as return typess or parameter types).

Using StandardMBean

If you are working with an earlier Java release than JDK 6, or if your MBean can't be turned into an MXBean, then you have another possibility: you can wrap your MBean in a StandardMBean.

The javax.management.StandardMBean class will let you turn your standard MBean into a DynamicMBean, and select its Management Interface. The interface must be implemented by the implementation class, but its name doesn't need to follow any naming convention. It simply needs to be a valid MBean interface.

---------------------------------------------------------------------------
// Interface can be in any package.
package <interface-package-name>;

// No name convention required! You can keep the name YMBean, or change it
// to whatever you like, like YInterface
public interface YInterface {...}

---------------------------------------------------------------------------

package <package-name>;
import <interface-package-name>.YInterface;
public class YImpl implements YInterface {...}

public static void main(String[] args) {
   final MBeanServer server = ...;
   final ObjectName  name   = ...;
   final YImpl impl = new YImpl(...);
   final StandardMBean mbean = new StandardMBean(impl,YInterface.class);

   // mbean is an instance of StandardMBean: it's a DynamicMBean.
   server.registerMBean(mbean,name);
}

---------------------------------------------------------------------------

Interestingly, you can also use StandardMBean to wrap an MXBean. Since MXBeans do not have any constraints with regards to their interface names, this use case is obviously different. We will see in the next section some of the odd cases that might incite you into wrapping an MXBean inside a StandardMBean.

StandardMBean and MXBeans

The laws by which the MBeanServer decides whether an MBean is a DynamicMBean, a standard MBean or an MXBean may appear to have some strange side effects. Let's see how the MBeanServer discriminates between these three kinds of MBeans:

First, the MBeanServer looks whether the provided MBean implements the DynamicMBean interface. If so, it's a Dynamic MBean, end of story.
Then the MBeanServer determines whether the provided MBean can be a standard MBean. If it does, or if one of its superclass does, then it's a standard MBean.
Lastly, if the MBean is neither a DynamicMBean nor a standard MBean, the MBeanServer looks whether it could be an MXBean.
If neither case apply, the MBeanServer will throw a NotCompliantMBeanException.

This leads to the following situations:

---------------------------------------------------------------------------
package example;

// Let's define some interfaces
public interface OneMBean { }
public interface TwoMBean extends OneMBean { }
public interface ThreeMXBean extends TwoMBean { }
public interface FourMBean extends ThreeMXBean { }

// Now let's define M(X)Beans that implement them...

// 'One' is a standard MBean
// Its management interface is 'OneMBean'.
public class One implements OneMBean { }

// 'Two' is a standard MBean, not an MXBean
// Its management interface is 'TwoMBean'.
public class Two implements ThreeMXBean { }

// 'TwoAndHalf' is an MXBean, not a standard MBean
// Its management interface is defined by 'ThreeMXBean'.
public class TwoAndHalf implements ThreeMXBean { }

// 'Three' is a standard MBean, not an MXBean
// Its management interface is 'TwoMBean'.
public class Three extends Two implements ThreeMXBean { }

// 'ThreeAndHalf' is an MXBean, not a standard MBean
// Its management interface is defined by 'ThreeMXBean'.
public class ThreeAndHalf extends TwoAndHalf;

// 'Four' is a standard MBean, not an MXBean
// Its management interface is 'FourMBean'.
public class Four extends ThreeAndHalf implements FourMBean;

---------------------------------------------------------------------------

The reasons why it is so have been explained above:

  • Two is an MBean because it implements TwoMBean. The pair (class example.Two, interface example.TwoMBean) thus satisfies the standard MBean pattern.
  • TwoAndHalf is not a standard MBean because it doesn't implement any interface called TwoAndHalfMBean, and none of its superclass is a standard MBean. However, TwoAndHalf implements ThreeMXBean. Therefore it's an MXBean.
  • Three is a standard MBean because one of its superclass (Two) is itself an MBean.
  • ThreeAndHalf is not a standard MBean because it doesn't implement any interface called ThreeAndHalfMBean, and none of its superclass is a standard MBean. However, ThreeAndHalf implements ThreeMXBean. Therefore it's an MXBean.
  • Finally Four is an MBean because it implements FourMBean. The pair (class example.Four, interface example.FourMBean) thus satisfies the standard MBean pattern.

This is where the class StandardMBean becomes interesting. Since StandardMBean removes all constraints on the naming of standard MBean interfaces, then StandardMBean also lets you decide whether to wrap your MBean as a standard MBean, or as an MXBean. Indeed StandardMBean has a constructor that will let you pass an isMxBean flag which tells whether the management interface you're selecting should be interpreted as a standard MBean interface or as an MXBean interface.

    final Three three = new Three(...);
    final StandardMBean mbean = 
        new StandardMBean(three,ThreeMXBean.class,true);
    
    // The MBean registered under 'name1' is a standard MBean.
    // Its interface is TwoMBean.
    server.registerMBean(three,name1);
    
    // The MBean registered under name2 is an MXBean. 
    // It's interface is ThreeMXBean
    server.registerMBean(mbean,name2);

The use case presented above is however a corner case, and it's very unlikely that you will ever need to use StandardMBean in this way. The next section will discuss some more interesting usages of the StandardMBean class, which apply both to wrapped MBean and wrapped MXBeans.

Customizing MBeanInfo

The principal use case for using StandardMBean, aside from selecting explicitely the management interface that should be exposed, is customization of the names and description exposed by the MBeanInfo.

Indeed the StandardMBean class defines a set of protected methods which provide convenient customization hooks to supply customized descriptions, parameter names, and operation impacts.

Having your MBean extend StandardMBean

To implement the customization hooks, the simplest way is to have your MBean simply extend StandardMBean:

---------------------------------------------------------------------------

public interface ThingMBean {
    public String getSomeItem();
    public String doWhatsIt(String thingummyjig);
}

---------------------------------------------------------------------------

public class Thing extends StandardMBean implements ThingMBean {

    Thing() throws NotCompliantMBeanException {
        super(ThingMBean.class);
    }
 
    public String getSomeItem() { ... }
    public String doWhatsIt(String thingummyjig) { ... }
    
    // Override customization hook:
    // Supply a customized description for MBeanInfo.getDescription();
    //
    protected String getDescription(MBeanInfo info) {
        return "A ThingMBean is an MBean that performs things.";
    }
    
    // Override customization hook:
    // Supply a customized description for attribute "SomeItem"
    //
    protected String getDescription(MBeanAttributeInfo info) {
        String description = null;
        if (info.getName().equals("SomeItem")) {
            description = 
               "This attribute represents some item obtained from a thing.";
        }
        return description;
    }
    
    // Override customization hook:
    // Supply a customized description for parameters of
    // of "doWhatsIt"
    //
    protected String getDescription(MBeanOperationInfo op,
            MBeanParameterInfo param,
            int sequence) {
        if (op.getName().equals("doWhatsIt")) {
            switch (sequence) {
                // 0 is first parameter: "thingummyjig"
                case 0: return "A contraption used for whatsIt";
                default : return null;
            }
        }
        return null;
    }
    
    // Override customization hook:
    // Supply a customized name for parameters of
    // of "doWhatsIt"
    //
    protected String getParameterName(MBeanOperationInfo op,
            MBeanParameterInfo param,
            int sequence) {
        if (op.getName().equals("doWhatsIt")) {
            switch (sequence) {
                // 0 is first parameter: "thingummyjig"
                case 0: return "thingummyjig";
                default : return null;
            }
        }
        return null;
    }
    
    // Override customization hook:
    // Supply a customized name for operation "doWhatsIt"
    //
    protected String getDescription(MBeanOperationInfo info) {
        String description = null;
        MBeanParameterInfo[] params = info.getSignature();
        String[] signature = new String[params.length];
        for (int i = 0; i < params.length; i++)
            signature[i] = params[i].getType();
        String[] methodSignature;
        
        methodSignature = new String[] {
            java.lang.String.class.getName()
            };
        if (info.getName().equals("doWhatsIt") && 
            Arrays.equals(signature, methodSignature)) {
            description = "Performs whatsit with a thingummyjig";
        }
        
        return description;
    }
}

---------------------------------------------------------------------------

The table below shows what you see in JConsole before and after customization:

Before Customization
After Customization

If you are using the JMX plugin for NetBeans, and if you use the wizzard to create a JMX MBean that extends StandardMBean, then NetBeans will automatically generate the implementations of these hooks for you, based on the various descriptions that you supply in the JMX MBean creation wizzard.

A small variation of the above use case is discussed below.
Rather than having your MBean extend StandardMBean, you can also choose to implement a subclass of StandardMBean dedicated to your MBean interface:

---------------------------------------------------------------------------

public interface ThingMBean {
    public String getSomeItem();
    public String doWhatsIt(String thingummyjig);
}

---------------------------------------------------------------------------

public class Thing implements ThingMBean {
    public Thing() { ... } 
    public String getSomeItem() { ... };
    public String doWhatsIt(String thingummyjig) { ... };
}

---------------------------------------------------------------------------

public class StandardThing extends StandardMBean {
    public StandardThing(ThingMBean athing) throws NotCompliantMBeanException {
        super(athing,ThingMBean.class);
    }
    
    // All customization hooks implemented as shown above.
    // 
    protected String getDescription(....) { ... }
}

---------------------------------------------------------------------------
                

Finally, you could also create a generic subclass of StandardMBean that would load all descriptions from e.g. a localized ResourceBundle.

Create a generic subclass of StandardMBean

Since this blog is starting to become quite long, I will not give the full implementation of such a class here. This might be the subject of another blog. Let me however give an outline:

---------------------------------------------------------------------------

public class LocalizedStandardMBean extends StandardMBean {

    private final ResourceBundle bundle;
    
    private static String getBundleNameFor(Class interfaceClass) {
        return ....;
    }
    
    public <T> LocalizedStandardMBean(T impl, Class<T> interfaceClass, 
                  boolean isMxBean, Locale locale) {
        super(impl,interfaceClass,isMxBean);
        bundle=ResourceBundle.getBundle(getBundleNameFor(interfaceClass),locale);
    }
    
    protected String getDescription(....) { 
        // Use 'bundle' to extract localized description.
        return ... ;
    }
    ...
}

---------------------------------------------------------------------------

However, creating a generic subclass of StandardMBean has its own limitations. Since you will need to accomodate both MBeans which are NotificationEmitters and MBeans which are not, you will also most probably need to create a similar subclass of StandardEmitterMBean too. This point is exposed below.

Other use cases

An other common use case for using StandardMBean is when you want to intercept all calls to a particular MBean in order to perform some pre/post operations. For instance, let's assume that I want to log all setAttribute actions performed on a particularly sensitive MBean. One way to do that would be to wrap that MBean in a subclass of StandardMBean, in which I would override the setAttribute method.

---------------------------------------------------------------------------

public class SetLoggingStandardMBean extends StandardMBean {
    private final Logger logger;
    public SetLoggingStandardMBean(T impl, Class<T> interfaceClass, 
                  boolean isMxBean) {
        super(impl,interfaceClass,isMxBean);
        logger = ....;
    }
   
    // This method is defined in the DynamicMBean interface, and
    // implemented by StandardMBean. Don't forget that StandardMBean turns 
    // your standard MBean into a DynamicMBean!
    // We override this method here to perform some logging before and 
    // after setting an attribute.
    //
    public void setAttribute(Attribute attr) 
                  throws AttributeNotFoundException,
                         InvalidAttributeValueException,
                         MBeanException,
                         ReflectionException {
        // Log...
        logger.config("Setting attribute: " + attr.getName() + "=" + 
                    attr.getValue());
        Exception failed = null;
        try {
            super.setAttribute(attr);
        } catch (AttributeNotFoundException x) {
            failed=x; throw x;
        } catch (InvalidAttributeValueException x) {
            failed=x; throw x;
        } catch (MBeanException x) {
            failed=x; throw x;
        } catch (ReflectionException x) {
            failed=x; throw x;
        } catch (RuntimeException x) {
            failed=x; throw x;
        } finally {
            if (failed == null) { // set was OK
                logger.config("attribute "+ attr.getName() + " successfully set");
            } else { // set failed
                logger.config("failed to set attribute "+ attr.getName() + 
                        ": " + failed);
            }
        }
    }
}

---------------------------------------------------------------------------

Known Hassles and Limitations

As I have already hinted in the previous paragraph, all is not completely trivial with StandardMBean. In this section we will discuss some points that need to be taken into account.

Notification Emitters

If your MBean is a NotificationEmitter, then you must not wrap it directly using StandardMBean. You must use StandardEmitterMBean instead.

Indeed, StandardMBean does not implement the NotificationEmitter interface. Otherwise, any MBean wrapped in a StandardMBean would be seen as a NotificationEmitter. Therefore the rule is:

  • If your MBean is not a notification emitter, use StandardMBean, or a subclass of StandardMBean which does not implement the NotificationEmitter interface.
  • If your MBean is a notification emitter, use StandardEmitterMBean, or a subclass of StandardEmitterMBean.

StandardEmitterMBean is a subclass of StandardMBean, and therefore all the tips I have given for StandardMBean in this blog also apply to StandardEmitterMBean.

A side effect of needing two classes (StandardMBean, StandardEmitterMBean) depending on whether an MBean is a NotificationEmitter or not, is that each time you find a use case that requires to extend StandardMBean, you're probably going to need to extend StandardEmitterMBean as well. Eventually, this can lead to quite complex combinatory situations. I would therefore advise to analyze the situation carefully before deciding to provide a generic purpose subclass of StandardMBean. In my next blog, I'll show how this pitfall can sometime be avoided.

MBeanRegistration

Another point worth noting is that StandardMBean does not forward callbacks from the MBeanRegistration interface to its wrapped MBean implementation. If your MBean implements MBeanRegistration, and if you wrap it in a StandardMBean - or in a StandardEmitterMBean, its preRegister method etc... will not be called. There's already RFE 6450834 logged for that - and it will hopefully be fixed in JDK 7.

In the mean time you may want to use your own subclass of StandardMBean if you need to work around that, or wait for my next blog, which will provide a more generic solution.

Constructors

You may not have noticed, but wrapping an MBean in a StandardMBean (using new StandardMBean(...);) instead of registering it directly, has the side effect of making all MBeanConstructorInfo disapear from its MBeanInfo. This is not a bug, but a conscious choice. The constructors exposed are those of the wrapped implementation, and would not allow to re-create an MBean wrapped in a StandardMBean instance.

The only case where constructors are kept is when you make your own MBean extend StandardMBean, and when the wrapped implementation is this. In that case, the StandardMBean is the MBean, and may therefore be reconstructed using createMBean.

ClassLoaders

Finally, here is the most obscure point. Sometimes, a JMXConnectorServer will need to figure out which ClassLoader to use when deserializing parameters sent by a remote client. It usually calls MBeanServer.getClassLoaderFor() for that purpose. The MBeanServer will return the class loader from which the MBean class was loaded. So if your MBean is a Thing, the MBeanServer will return Thing.getClass().getClassLoader(). However, if your MBean was wrapped in a StandardMBean, what is returned is StandardMBean.class.getClassLoader(), which happens to be null (on JDK 5 and later) since StandardMBean is in rt.jar. So if your MBean happens to use custom types, is not an MXBean, and is not on the CLASSPATH, it usually result in a ClassNotFoundException fired back to the client.

One way to work around this is usually to create an anonymous subclass of StandardMBean when registering your MBean - e.g. something like:

    // Creating an anonymous subclass works only if you create it from a class
    // that was loaded by a ClassLoader that has access to your MBean class
    // ('Thing' in this case).
    // This is obviously the case in this snippet of code, because we were
    // able to write: final Thing thing = new Thing(...);
    //
    final Thing thing = new Thing(...);
    final StandardMBean mbean = new StandardMBean(thing, ThingMBean.class) { };
    server.registerMBean(mbean,name);

Don't be too worried, it's unlikely that you will need to resort to such tricks.

The End

So, this is the end of my long blog, and I hope you have enjoyed your journey in the bowels of StandardMBean.

Next time I'll expose a smart StandardMBeanFactory that will work around the MBeanRegistration problem.

Stay tuned to JMX, SNMP, Java, et caetera!

Cheers
-- daniel

See also: DynamicMBeans, ModelMBeans, and Pojos...
Look also for other JMX related articles in this blog...
Comments:

It would seem to me that making a trivial subclass - the { } bit - indicates a design defect in either MBeanServer, StandardMBean, or both. It should be possible, in the case of a StandardMBean, to automatically use the classloader of the wrapped instance's class. As you pointed out to me, it's necessary to get the classLoader right in order for isInstanceOf queries to work, so it applies even if you're careful to have your management interface only consist of simple types. Incidentally, it appears that the comment preview function on blogs.sun.com is vulnerable to a type 1 XSS attack. Whom should I tell about that?

Posted by Daniel Martin on March 14, 2007 at 02:56 PM CET #

Hi Martin,
Yes you are right, the {} bit is a workaround to a design flaw. Unfortunately design flaws happen ;-\\. Sometimes you can fix them, and sometimes you have to live with the workaround. In this particular case, could we fix it, or would we introduce an incompatibilty?
I am not sure what would be the best response...
My own idea is that we should extend the DynamicMBean interface (= introduce a subclass of DynamicMBean) which would have an isInstanceOf method. StandardMBean could implement this new interface. But it would still leave us with the possible incompatibility issue...
As to vulnerability of blogs.sun.com, you can send it to me (you know who I am and where I work ;-)), and I will forward...

Posted by daniel on March 14, 2007 at 07:24 PM CET #

Daniel,

Thanks for the good insight into JMX beans.

The problem we had been facing was maintaining the documentation for MBeans in two places. Once in Javadoc of Mbean interface and once in java file (or resource file as you mentioned) that gives to jmx agents at runtime. To mitigate this, I have created a small doclet, which can generate this java file with all javadoc documentation automatically populated.

http://civiccenterdr.blogspot.com/2007/10/standard-mbeans-and-documentation.html

Hope this would be helpful.

Posted by Santhosh on October 27, 2007 at 03:20 PM CEST #

@Santhosh
Indeed, this is interesting... Thanks for the link!

Posted by daniel on November 06, 2007 at 12:03 PM CET #

Hello,

Thanks for the explantion.
Just one question is there an overhead if using StandardMBean ?

Posted by Fraer9 on September 24, 2009 at 10:35 AM CEST #

Hi @Fraer9

There shouldn't be much overhead: if you use a regular MBean (a standard MBean in 2 words), the MBeanServer will use reflection to invoke methods on your MBean. If you use a StandardMBean, it's the StandardMBean that will do the reflection.

In either case - it's pretty much the same code that will be triggered.

-- daniel

Posted by daniel on September 24, 2009 at 10:57 AM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Daniel Fuchs blogs on Scene Builder, JMX, SNMP, Java, etc...

The views expressed on this blog are those of the author and do not necessarily reflect the views of Oracle.

Search

Categories
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