|
|
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 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.
To get rid of these naming convention constraints, you have two
main choices:
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).
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.
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.
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.
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.
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.
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);
}
}
}
}
---------------------------------------------------------------------------
|
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.
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.
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.
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.
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.
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...
Tags:
java
jmx
management
mbean
Posted by dfuchs
( Jan 09 2007, 06:14:11 PM CET )
Permalink
|
Posted by Daniel Martin on March 14, 2007 at 09:56 PM CET #
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 15, 2007 at 02:24 AM 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 10:20 PM CEST #
@Santhosh
Indeed, this is interesting... Thanks for the link!
Posted by daniel on November 06, 2007 at 08: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 05:35 PM 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 05:57 PM CEST #