A real example of a Dynamic MBean
The JMX API includes the possibility to create "Dynamic MBeans", whose management interface is determined at run time. When might that be useful? Here's an example.
In the JMX forum on the Sun Developer Network, Athar asks how to load a properties file in a dynamic MBean. I think that's an excellent question, because it's exactly the example I usually use for "Runtime" Dynamic MBeans.
What I call a "Runtime" Dynamic MBean is one whose management
interface you cannot determine by looking at the source code.
Obviously Standard MBeans aren't like this, because you can just
look at WhateverMBean.java to see what the
management interface is going to be. This is still true for
MBeans constructed using the StandardMBean
class, and it's also true for MXBeans.
It isn't necessarily true for Dynamic MBeans. A Dynamic MBean is a Java object of a class that implements the DynamicMBean interface. This interface includes a method getMBeanInfo(). A class that implements DynamicMBean can construct the MBeanInfo object that it returns from getMBeanInfo() however it likes. It can even return a different MBeanInfo every time it is called!
This flexibility is almost never necessary. Nearly always, when you create a Dynamic MBean, it is because you want to add extra information to the MBeanInfo, or because you want to implement the logic to get an attribute or call an operation in some particular way. Just like dynamic code generation, my advice if you are considering making a Runtime Dynamic MBean is to think really hard about whether you couldn't redesign things so that the interface is known at compile time. The problem with an MBean interface only known at run time is that it's hard for a client to interact with it. Suppose your client wants to call getAttribute on your MBean. The only way it can know what attributes are available is to call getMBeanInfo beforehand. If the MBean's interface can change as it is running, even this isn't guaranteed to work!
However, there are some cases where it makes a certain amount of sense to have a Runtime Dynamic MBean, and Athar's question suggests one of them. Suppose you have a properties file containing configuration for your application, and you'd like to expose its contents for management, so that you can see the values of configuration items, and perhaps change them as the application is running. The obvious way to do this is to have a ConfigurationManagementMBean that is linked to the properties file.
Every time you change your app to add a new configuration item, you'll need to add it to the initial configuration file, and you'll need to add code to interpret it. But it would be a pain to have to add a new attribute explicitly to the ConfigurationManagementMBean as well. So this argues for one of two approaches:
- The ConfigurationManagementMBean has one big attribute that
is a
Properties, say, or aMap<String,String>if you're using MXBeans. This is workable, but in practice it is very clumsy. In order to change a configuration item, you'll have to get the complete set of items, change the item within it, and write the result back to the MBean. Furthermore, JConsole doesn't currently support changing an item in the middle of aPropertiesorMapattribute. - The ConfigurationManagementMBean has one attribute per configuration item, or in other words per property. It determines these attributes at run time by reading the properties file. So it's a Runtime Dynamic MBean!
If you adopt the second approach, then JConsole looking at your ConfigurationManagementMBean might look like this:
I'll present the code to implement this below. A few things are worth noting. First of all, the DynamicMBean interface is a little bit clunky, in particular the getAttributes and (especially) setAttributes methods. The problem that generates this clunkiness is what to do if one of the attributes to be set produces an error. Should you throw an exception? If so, have any of the other attributes been set? The cleanest solution would be to say that setAttributes is an all-or-nothing operation: either it sets all of the given attributes, or it sets none of them and throws an exception. However, the designers of the JMX API felt that this was a harsh constraint to put on MBean writers. What's more it is not at all obvious how it should apply to Standard MBeans. So instead, setAttributes returns an AttributeList containing the attributes that were actually set. The caller needs to check that this contains all the values that were supposed to be set, and react appropriately if not.
The code doesn't let you set a value for a property that was not already present. The MBean Server does not check that the attribute name in setAttribute is present in the MBeanInfo. It is up to the MBean to do that. An MBean could choose to accept such a name, which in this case would allow you to define new properties. But I think it would be better to achieve that in some other way, for example an explicit addProperty operation.
In addition to one attribute per property, I've defined an
operation reload which reloads the properties from
the file. If there are properties in the file that were not
present before, then they will appear as new attributes. Notice
that adding an operation requires you both to mention it in
getMBeanInfo and to recognize it in
invoke. If there are many operations, you might
want to consider getting the StandardMBean
class to do some of the work for you.
Finally, every time you change a property the code updates the configuration file. The way it does this is intended to be a safe way to update a file. It writes a new properties file in the same directory, then renames it over the original. On most operating systems, renaming is atomic, so even if your app is interrupted in the middle of this operation, you will end up with either the old file or the new file, but not with a missing or partially-written file.
package propertymanager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.ReflectionException;
public class PropertyManager implements DynamicMBean {
private final String propertyFileName;
private final Properties properties;
public PropertyManager(String propertyFileName) throws IOException {
this.propertyFileName = propertyFileName;
properties = new Properties();
load();
}
public synchronized String getAttribute(String name)
throws AttributeNotFoundException {
String value = properties.getProperty(name);
if (value != null)
return value;
else
throw new AttributeNotFoundException("No such property: " + name);
}
public synchronized void setAttribute(Attribute attribute)
throws InvalidAttributeValueException, MBeanException, AttributeNotFoundException {
String name = attribute.getName();
if (properties.getProperty(name) == null)
throw new AttributeNotFoundException(name);
Object value = attribute.getValue();
if (!(value instanceof String)) {
throw new InvalidAttributeValueException(
"Attribute value not a string: " + value);
}
properties.setProperty(name, (String) value);
try {
save();
} catch (IOException e) {
throw new MBeanException(e);
}
}
public synchronized AttributeList getAttributes(String[] names) {
AttributeList list = new AttributeList();
for (String name : names) {
String value = properties.getProperty(name);
if (value != null)
list.add(new Attribute(name, value));
}
return list;
}
public synchronized AttributeList setAttributes(AttributeList list) {
Attribute[] attrs = (Attribute[]) list.toArray(new Attribute[0]);
AttributeList retlist = new AttributeList();
for (Attribute attr : attrs) {
String name = attr.getName();
Object value = attr.getValue();
if (properties.getProperty(name) != null && value instanceof String) {
properties.setProperty(name, (String) value);
retlist.add(new Attribute(name, value));
}
}
try {
save();
} catch (IOException e) {
return new AttributeList();
}
return retlist;
}
public Object invoke(String name, Object[] args, String[] sig)
throws MBeanException, ReflectionException {
if (name.equals("reload") &&
(args == null || args.length == 0) &&
(sig == null || sig.length == 0)) {
try {
load();
return null;
} catch (IOException e) {
throw new MBeanException(e);
}
}
throw new ReflectionException(new NoSuchMethodException(name));
}
public synchronized MBeanInfo getMBeanInfo() {
SortedSet<String> names = new TreeSet<String>();
for (Object name : properties.keySet())
names.add((String) name);
MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[names.size()];
Iterator<String> it = names.iterator();
for (int i = 0; i < attrs.length; i++) {
String name = it.next();
attrs[i] = new MBeanAttributeInfo(
name,
"java.lang.String",
"Property " + name,
true, // isReadable
true, // isWritable
false); // isIs
}
MBeanOperationInfo[] opers = {
new MBeanOperationInfo(
"reload",
"Reload properties from file",
null, // no parameters
"void",
MBeanOperationInfo.ACTION)
};
return new MBeanInfo(
this.getClass().getName(),
"Property Manager MBean",
attrs,
null, // constructors
opers,
null); // notifications
}
private void load() throws IOException {
InputStream input = new FileInputStream(propertyFileName);
properties.load(input);
input.close();
}
private void save() throws IOException {
String newPropertyFileName = propertyFileName + "$$new";
File file = new File(newPropertyFileName);
OutputStream output = new FileOutputStream(file);
String comment = "Written by " + this.getClass().getName();
properties.store(output, comment);
output.close();
if (!file.renameTo(new File(propertyFileName))) {
throw new IOException("Rename " + newPropertyFileName + " to " +
propertyFileName + " failed");
}
}
}
- Login or register to post comments
- Printer-friendly version
- emcmanus's blog
- 12314 reads






Comments
Adding new properties
by zsee - 2010-07-14 08:56
Hi, "If there are properties in the file that were not present before, then they will appear as new attributes." Could you please explain how is this actually supposed to work? New attributes appear where? On the JMX console? Frankly, I can't see that happening...I guess I'm missing something.. That reload operation is supposed to make the MBeanServer to call the getMBeanInfo() method of the actual DynamicMBean, ain't it? Otherwise it will never know a new atteribute has appeared.. I realize this is a fairly old post, and thing might have been changed, but still I'm looking for exactly the behavior the quoted sentence suggests me (ie. the new attribute 'pops up' on JMX console automatically, in other words, the MBeanInfo gets refreshed as result of the reload() operation.) If you could explain this in bit more detail, I'd be grateful. CheersAdding new properties and removing existing ones.
by kprichards - 2010-07-15 04:16
I am new to JMX. I am also facing the same issue when I update the property file. The behaviour that we expect is that when we invoke reload() it should update the jconsole as well. Assume that I had 4 key-value pairs in the property file initially. If I remove one of it and then invoke the reload(), the jconsole attribute shows that the deleted property is unavailable instead of removing it from the attribute list. Similarly if I add a new property into the file, it is not getting added in the jconsole. But once I disconnect and reconnect jconsole, the new properties would be displayed properly. Is this a limitation with jconsole? I think the getMBeanInfo() is not getting invoked after we invoke reload() as told in the previous comment. I even tried to invoke it explicitly in the load(). But even then the results were the same. I saw this on net: http://robey.lag.net/2009/03/29/anyone-use-jmx.html I saw some other posts like : http://weblogs.java.net/blog/2006/06/08/adding-descriptors-mbeans-tiger, http://weblogs.java.net/blog/emcmanus/archive/2005/10/adding_descript.html Can somebody suggest a solution with respect to this example.Adding new properties and removing existing ones.
by emcmanus - 2010-07-18 12:14
Indeed, this is a shortcoming in the existing JConsole and JVisualVM tools. For performance reasons, they cache the MBeanInfo of every MBean they display, which means that they do not notice if it subsequently changes. Ideally, they would notice when an MBean has immutableInfo=false in its Descriptor, and not cache in that case. They could ultimately also recognize the jmx.mbean.info.changed notification which will be documented in JDK7 (see MBeanInfo in the draft JDK7 javadoc).
For now, the simplest way to get JConsole or JVisualVM to react to change in the MBeanInfo is to unregister the MBean and immediately register it again.
Dynamic MBean for loading properties
by agebee - 2010-03-10 08:22
Hello, very good article about using JMX as a means for loading the settings for an application. I see the most benefit from your solution when you implement the MBean to generate a notification for the application to signalize if a property changed. So whenever a change is made (e.g. through an administrator) the MBean fires and the application gets the latest entry for that property. Have you made any experiences in that way ? Regards, AgeBeeI am trying to develop this
by khurle - 2010-10-29 14:46
I am trying to develop this dynamic MBean on my local workstation running Tomcat and test it there running jconsole. But for Production, it will be deployed to WebSphere.
A couple questions:
How do I deploy this thing? Do I package it in its own jar file? Do I include it with my server source?
Is it just the example code above?
No deployment descriptors?
What configuration files need to be modified for Tomcat?
Do I need to deploy an mbeans-descriptor.xml? And if so, what goes in there?
Thanks,
Kevin