Search |
||
Creating type-safe MBean proxiesPosted by emcmanus on July 6, 2006 at 10:04 AM PDT
MBean proxies allow you to access an MBean through a Java interface, writing proxy.getFoo() instead of mbeanServer.getAttribute(name, "Foo"). But when you create a proxy, there is no check that the MBean actually matches the interface you specify, or even that the MBean exists. Why is that, and what can you do about it? Here's a more concrete example. Suppose you have an MBean interface like this:
public interface CacheMBean {
public int getSize();
public void setSize(int x);
public void dropOldest(int nEntries);
}
Also suppose that you have registered an MBean answering to
that interface with a certain CacheMBean proxy = JMX.newMBeanProxy(mbeanServer, objectName, CacheMBean.class); Well, that's the neato Mustang (Java SE 6) version. If you're using an earlier version, like Tiger (J2SE 5.0), then it's a bit more verbose:
CacheMBean proxy = (CacheMBean)
MBeanServerInvocationHandler.newProxyInstance(mbeanServer, objectName,
CacheMBean.class, false);
(Of course that will continue to work on Mustang, but the first version is so much nicer that you'll want to use it if you can.) Either way, this allows you to write things like: proxy.setSize(proxy.getSize() * 2); proxy.dropOldest(25); instead of the code you would have to write without the proxy:
int size = mbeanServer.getAttribute(objectName, "Size");
mbeanServer.setAttribute(objectName, new Attribute("Size", size * 2));
mbeanServer.invoke(objectName, "dropOldest", new Object[] {25},
new String[] {"int"});
It's clear that the version with the proxy is much simpler to read and write, and much safer too since the compiler will check that the methods you are invoking are indeed in the interface and that you are using the right types. That's why we recommend using proxies like this when at all possible. Poxy proxyBut all is not completely rosy. What happens for example if
the MBean doesn't exist? You might expect that creating the proxy
would fail in that case. But it doesn't. You can go right ahead
and make your proxy, and then when you call
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at $Proxy0.getSize(Unknown Source)
at typesafeproxy.Test.main(Test.java:37)
Caused by: javax.management.InstanceNotFoundException: somedomain:type=Cache
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getMBean(DefaultMBeanServerInterceptor.java:1094)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getAttribute(DefaultMBeanServerInterceptor.java:662)
at com.sun.jmx.mbeanserver.JmxMBeanServer.getAttribute(JmxMBeanServer.java:638)
at javax.management.MBeanServerInvocationHandler.invoke(MBeanServerInvocationHandler.java:262)
... 2 more
You'll immediately wonder
The We generally recommend that you declare
public interface CacheMBean {
public int getSize() throws IOException;
public void setSize(int x) throws IOException;
public void dropOldest(int nEntries) throws IOException;
}
However,
Pesky proxyNevertheless, usually you do know that your MBeans aren't suddenly going to disappear or delete their attributes, and in that case you might be happier if the checks were made upfront when the proxy is created. Can we achieve that somehow? The answer is that we can. To start with, here's a simple class that defines a method that creates an MBean proxy, but throws an exception if the proxied MBean doesn't exist.
public class ExistentJMXProxy {
private ExistentJMXProxy() {} // no instances of this class
public static <T> T newMBeanProxy(
MBeanServerConnection mbsc,
ObjectName name,
Class<T> intfClass)
throws IOException, InstanceNotFoundException {
// Provoke IOException or InstanceNotFoundException now
// rather than later
mbsc.getObjectInstance(name);
return intfClass.cast(
MBeanServerInvocationHandler.newProxyInstance(
mbsc, name, intfClass, false));
}
}
Now you can write We make a call to The magic with Prickly proxyWe might be satisfied with our existence check, but in fact
just because an MBean exists with the name we gave doesn't mean
it's the right one. Nothing is stopping you from
creating a If you're very familiar with the JMX API, you might think that
a good and simple way to make this check would be to use
if (!mbsc.isInstanceOf(name, intfClass.getName()))
throw new InstanceNotFoundException("Wrong type MBean: " + name);
But actually that isn't a great idea. It will work if the
MBean is a Standard MBean that implements the
The problem is that Prolix proxySo what we want to do is to check, when creating the proxy, that the named MBean exists, and that it has all the attributes and operations that the proxy can access. How might we go about doing that? The simplest way is to generate the We don't have to require the two
So let's look at some code. We're going to make a class
public class TypeSafeJMXProxy {
/** There are no instances of this class. */
private TypeSafeJMXProxy() {
}
/**
* Create an MBean proxy, checking that the target MBean exists
* and implements the attributes and operations defined by the
* given interface.
*
* @param mbsc the MBean Server in which the proxied MBean is registered.
* @param name the ObjectName under which the proxied MBean is registered.
* @param intfClass the MBean interface that the proxy will
* implement by forwarding its methods to the proxied MBean.
*
* @return The newly-created proxy.
*
* @throws IOException if there is a communication problem when
* connecting to the {@code MBeanServerConnection}.
* @throws InstanceNotFoundException if there is no MBean
* registered under the given {@code name}.
* @throws NotCompliantMBeanException if {@code intfClass} is
* not a valid MBean interface.
* @throws NoSuchMethodException if a method in
* {@code intfClass} does not correspond to an attribute or
* operation in the proxied MBean.
*/
public static <T> T newMBeanProxy(
MBeanServerConnection mbsc,
ObjectName name,
Class<T> intfClass)
throws IOException, InstanceNotFoundException,
NotCompliantMBeanException, NoSuchMethodException {
// Get the MBeanInfo, or throw InstanceNotFoundException
final MBeanInfo mbeanInfo;
try {
mbeanInfo = mbsc.getMBeanInfo(name);
} catch (InstanceNotFoundException e) {
throw e;
} catch (JMException e) {
// IntrospectionException or ReflectionException:
// very improbable in practice so just pretend the MBean wasn't there
// but keep the real exception in the exception chain
final String msg = "Exception getting MBeanInfo for " + name;
InstanceNotFoundException infe = new InstanceNotFoundException(msg);
infe.initCause(e);
throw infe;
}
// Construct the MBeanInfo that we would expect from a Standard MBean
// implementing intfClass. We need a non-null implementation of intfClass
// so we create a proxy that will never be invoked.
final T impl = intfClass.cast(Proxy.newProxyInstance(
intfClass.getClassLoader(), new Class<?>[] {intfClass}, nullIH));
final StandardMBean mbean = new StandardMBean(impl, intfClass);
final MBeanInfo proxyInfo = mbean.getMBeanInfo();
checkMBeanInfos(intfClass.getClassLoader(), proxyInfo, mbeanInfo);
return intfClass.cast(MBeanServerInvocationHandler.newProxyInstance(
mbsc, name, intfClass, false));
}
The trick we use to get the But to create the MBean we need an object that implements the
MBean interface. We can use a dynamic
proxy to get that object. No method in the object will
ever actually be called, since we throw away the MBean as soon
as we've extracted its
private static class NullInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) {
return null;
}
}
private static final NullInvocationHandler nullIH =
new NullInvocationHandler();
The call to OK, we've got the
private static void checkMBeanInfos(
ClassLoader loader, MBeanInfo proxyInfo, MBeanInfo mbeanInfo)
throws NoSuchMethodException {
// Check that every attribute accessible through the proxy is present
// in the MBean.
MBeanAttributeInfo[] mais = mbeanInfo.getAttributes();
attrcheck:
for (MBeanAttributeInfo pai : proxyInfo.getAttributes()) {
for (MBeanAttributeInfo mai : mais) {
if (compatibleAttributes(loader, pai, mai))
continue attrcheck;
}
final String msg =
"Accessing attribute " + pai.getName() + " would fail";
throw new NoSuchMethodException(msg);
}
// Check that every operation accessible through the proxy is present
// in the MBean.
MBeanOperationInfo[] mois = mbeanInfo.getOperations();
opcheck:
for (MBeanOperationInfo poi : proxyInfo.getOperations()) {
for (MBeanOperationInfo moi : mois) {
if (compatibleOperations(loader, poi, moi))
continue opcheck;
}
final String msg =
"Accessing operation " + poi.getName() + " would fail";
throw new NoSuchMethodException(msg);
}
}
Notice that we're comparing every attribute in
For every attribute in
These rules allow the MBean to have an attribute that is read/write even though the proxy is read-only or write-only. They are a bit too strict, in that we could allow the type of a write-only attribute in the proxy to be a subclass of the type in the MBean; but write-only attributes hardly ever occur so we ignore that case.
private static boolean compatibleAttributes(
ClassLoader loader,
MBeanAttributeInfo proxyAttrInfo, MBeanAttributeInfo mbeanAttrInfo) {
if (!proxyAttrInfo.getName().equals(mbeanAttrInfo.getName()))
return false;
if (!proxyAttrInfo.getType().equals(mbeanAttrInfo.getType())) {
if (proxyAttrInfo.isWritable())
return false; // type must be identical
if (!isAssignable(loader,
proxyAttrInfo.getType(), mbeanAttrInfo.getType()))
return false;
}
if (proxyAttrInfo.isReadable() && !mbeanAttrInfo.isReadable())
return false;
if (proxyAttrInfo.isWritable() && !mbeanAttrInfo.isWritable())
return false;
return true;
}
Similar logic applies for operations. The return type of an operation in the MBean can be a subclass of the return type in the proxy but otherwise everything must match exactly.
private static boolean compatibleOperations(
ClassLoader loader,
MBeanOperationInfo proxyOpInfo, MBeanOperationInfo mbeanOpInfo) {
if (!proxyOpInfo.getName().equals(mbeanOpInfo.getName()) ||
!isAssignable(loader,
proxyOpInfo.getReturnType(),
mbeanOpInfo.getReturnType()))
return false;
MBeanParameterInfo[] proxyParams = proxyOpInfo.getSignature();
MBeanParameterInfo[] mbeanParams = mbeanOpInfo.getSignature();
if (proxyParams.length != mbeanParams.length)
return false;
for (int i = 0; i < proxyParams.length; i++) {
if (!proxyParams[i].getType().equals(mbeanParams[i].getType()))
return false;
}
return true;
}
Finally, we have to define what it means for a type in an
private static boolean isAssignable(
ClassLoader loader, String toClassName, String fromClassName) {
if (toClassName.equals(fromClassName))
return true;
try {
Class<?> toClass = Class.forName(toClassName, false, loader);
Class<?> fromClass = Class.forName(fromClassName, false, loader);
return toClass.isAssignableFrom(fromClass);
} catch (ClassNotFoundException e) {
// Could not load one of the two classes so consider not assignable
// In real code we might like to log the exception
return false;
}
}
}
Perplexing proxyWith that considerable wodge of code, we can now make proxies
and be sure they will work. So long as the MBean they're
connected to doesn't budge, anyway. In a future version of the
JMX API, we may add this functionality to an alternative form of
AcknowledgementThe idea for this article came from an e-mail exchange with Sanjay Radia, now at Cassatt. »
Related Topics >>
Open JDK Comments
Comments are listed in date ascending order (oldest first)
|
||
|