Saturday, June 21, 2008

The JMX MXBean

I'll demonstrate the tremendous utility of the JMX MXBean in this blog entry. While doing so, I'll also demonstrate the usefulness of a J2SE 5 enum for reporting a set of discrete status values and will demonstrate the highly useful JMX (introduced with Java SE 6) class's newMBeanProxy and newMXBeanProxy methods.

The MXBean was introduced in J2SE 5, but was only available for predefined system MXBeans (platform MXBeans). The ability for JMX developers to write their own custom MXBeans was provided with Java SE 6. With MXBeans, the "standard" set of JMX MBeans is now Standard MBean, Dynamic MBean, Model MBean (used heavily in Spring JMX), Open MBean, and MXBean. In many ways, the MXBean is like a souped-up standard MBean. It follows similar conventions as the standard MBean, but provides much greater support for representing custom data types in remote JMX clients.

The MXBean is so similar in usage to the Standard MBean that it is not surprising that the thorough MXBean introduction MXBeans in Java SE 6: Bundling Values without Special JMX Client Configurations recommends using MXBeans in any Java SE 6 application where Standard MBeans may have been used before moving to Java SE 6.

The standardized MXBean should not be confused with the JBoss-specific XMBean. The JBoss XMBean is not an MXBean, but is actually a JBoss Model MBean approach similar to how the Spring Framework approaches Model MBeans.

For my MXBean example in this blog entry, I will be taking advantage of one of my favorite features added to Java since the ubiquitous JDK 1.4.2: the Java enum. I wrote about the advantages of the Java enum for representing finite sets of related values in the OTN article Basic Java Persistence API Best Practices and in a previous blog entry The Power and Flexibility of the Java Enum. Because JMX is often used to access an application's state or status and because such a state or status can often be defined in a set of finite choices, the enum can be a highly useful construct in JMX applications.

For my demonstration, I will build the same functionality using both a Standard MBean and an MXBean. The next two listings show the Standard MBean interface (StatusMBean) and the very similar looking MXBean (StatusMXBean) interface. Note how they are nearly identical. The MXBean interface is only one byte larger than the MBean interface and that is due to the extra "X" added to the interface's name. They are identical other than that extra X. Therefore, it is easy to port a Standard MBean to an MXBean in terms of the MBean itself.

StatusMBean.java (Standard MBean interface)

package server;

public interface StatusMBean
{
public StatusEnum getStatus();

public void setStatus(final StatusEnum status);

public enum StatusEnum{ SUCCESSFUL, FAILURE, UNSPECIFIED };
}


StatusMXBean.java (MXBean Interface)

package server;

public interface StatusMXBean
{
public StatusEnum getStatus();

public void setStatus(final StatusEnum status);

public enum StatusEnum{ SUCCESSFUL, FAILURE, UNSPECIFIED };
}


With the Standard MBean and MXBean interfaces defined, it is time to look at their implementations. These are shown in the next two listings.

Status.java (Standard MBean implementation)

package server;

public class Status implements StatusMBean
{
private StatusEnum statusEnum = StatusEnum.UNSPECIFIED;

public Status() {}

public StatusEnum getStatus()
{
return this.statusEnum;
}

public void setStatus(final StatusEnum status)
{
this.statusEnum = status;
}
}


Status2.java (MXBean implementation)

package server;

public class Status2 implements StatusMXBean
{
private StatusEnum statusEnum = StatusEnum.UNSPECIFIED;

public Status2() {}

public StatusEnum getStatus()
{
return this.statusEnum;
}

public void setStatus(final StatusEnum status)
{
this.statusEnum = status;
}
}


The rules (or required conventions) for MXBeans are less strict than those for Standard MBeans. For example, you may have noticed that the MXBean implementation class shown above is actually Status2. I had to use simply Status.java for the implementation of the Standard MBean interface StatusMBean, but did not need to do so for StatusMXBean. If I was to try to do the same thing with the Standard MBean, say by adding a "3" on the end of the implementation class so that it is Status3.java, I would see a runtime error similar to that shown in the next screen snapshot when I tried to register that Standard MBean.



As the screen snapshot above indicates, not following the Standard MBean convention of naming the implementation identically to the interface sans the "MBean" suffix leads to a NotCompliantMBeanException with the message that the particular non-conforming implementation class "does not implement DynamicMBean, neither follows the Standard MBean conventions" and "is not a JMX compliant Standard MBean." The fact that we don't see this same error message for the Status2 implementation of StatusMXBean demonstrates that the MXBean naming conventions are looser than those for Standard MBeans.

Related to the last point, another potential advantage of the MXBean over the Standard MBean is that an MXBean implementation class can be in a different package than its defining MXBean interface. I'll not demonstrate it here, but Eamonn McManus explains in this JMX forum thread that MXBean does not have some of these restrictions that the Standard MBean has.

The looser naming convention and packaging structure of the MXBean can be nice advantages for certain situations, but I still have not demonstrated what is likely to be the biggest advantage most JMX developers enjoy with an MXBean. To see this advantage, it is now time to access the Standard MBean and MXBean shown above remotely.

The following class shows the registration of the Standard MBean and the MXBean with the MBeanServer. This class looks much more difficult than it really is. Most of the lines in the class are the handling of the checked exceptions thrown by the various calls and by the import statements for those exceptions. This class (SimpleMBeanServer) instantiates the Standard MBean and the MXBean and registers both with the MBean server.

SimpleMBeanServer.java

package server;

import java.lang.management.ManagementFactory;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MalformedObjectNameException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class SimpleMBeanServer
{
public static void registerMBean(
final MBeanServer mbs,
final String mBeanObjectName,
final Class mBeanClass)
{
try
{
final ObjectName name = new ObjectName(mBeanObjectName);
final Object mBean = mBeanClass.newInstance();
mbs.registerMBean(mBean, name);
}
catch (InstantiationException badInstance) // Class.newInstance()
{
System.err.println( "Unable to instantiate provided class with class "
+ "name " + mBeanClass.getName() + ":\n"
+ badInstance.getMessage() );
}
catch (IllegalAccessException illegalAccess) // Class.newInstance()
{
System.err.println( "Illegal Access trying to instantiate "
+ mBeanClass.getName() + ":\n"
+ illegalAccess.getMessage() );
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println( mBeanObjectName + " is a bad ObjectName:\n"
+ badObjectName.getMessage() );
}
catch (InstanceAlreadyExistsException duplicateMBeanInstance)
{
System.err.println( mBeanObjectName + " already existed as an MBean:\n"
+ duplicateMBeanInstance.getMessage() );
}
catch (MBeanRegistrationException mbeanRegistrationProblem)
{
System.err.println( "ERROR trying to register " + mBeanObjectName + ":\n"
+ mbeanRegistrationProblem.getMessage() );
}
catch (NotCompliantMBeanException badMBean)
{
System.err.println( "ERROR: " + mBeanObjectName + " is not compliant:\n"
+ badMBean.getMessage() );
}
}

public static void main(final String[] arguments)
{
final String mbeanObjectNameStr = "example:type=Status";
final String mxbeanObjectNameStr = "example:type=Status2";
//final String mbean3ObjectNameStr = "example:type=Status3";
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
registerMBean(mbs, mbeanObjectNameStr, Status.class);
registerMBean(mbs, mxbeanObjectNameStr, Status2.class);
//registerMBean(mbs, mbean3ObjectNameStr, Status3.class);
System.console().printf("Press ENTER to exit.");
final String dummyValue = System.console().readLine();
}
}


When running the MBean server code shown above, I have used the three JMX-related system properties to specify that it is remotely accessible and does not require any authentication:

-Dcom.sun.management.jmxremote.port=1199
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

With the server code written and running so that it is exposed to remote clients, it is time to turn to the client. We'll start with the readily available JConsole and then use our own client. The next couple of screen snapshots demonstrate how the Standard MBean's status (StatusEnum) is handled differently than the MXBean's status (also a StatusEnum). The first screen snapshot shows the JConsole display of the Standard MBean status attribute and the second screen snapshot demonstrates the JConsole display for the same status via the MXBean.

Standard MBean Enum Attribute Not Available


MXBean Enum Attribute Displayed Correctly


The most important difference in the above two screen snapshots is that the MXBean's attribute's value is correctly displayed, but the Standard MBean's enum attribute value is "Unavailable." Also note that JConsole provides additional details regarding the MXBean attribute type in the Descriptor section. This demonstrates another advantage of the MXBean.

We'll now move on to a custom client and call it SimpleClient. As with the server code, the checked exception handling and associated import statements make the functionality appear at first glance to be more complicated than it really is.

SimpleClient.java

package client;

import server.StatusMBean;
import server.StatusMXBean;

import java.io.IOException;
import java.net.MalformedURLException;

import javax.management.JMX;
import javax.management.MalformedObjectNameException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;

import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
* Simple remote JMX client intended to show differences seen from client
* perspective between a standard MBean and an MXBean.
*/
public class SimpleClient
{
public static String getServerStatusViaMBean(
final MBeanServerConnection mbsc )
{
String statusToReturn = "";
final String mbeanObjectNameStr = "example:type=Status";
try
{
final ObjectName objectName = new ObjectName(mbeanObjectNameStr);
final StatusMBean statusBeanProxy =
JMX.newMBeanProxy(mbsc, objectName, StatusMBean.class);
statusToReturn = statusBeanProxy.getStatus().toString();
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println( mbeanObjectNameStr + " is a bad object name.:\n"
+ badObjectName.getMessage() );
}

return statusToReturn;
}

public static String getServerStatusViaMXBean(
final MBeanServerConnection mbsc )
{
String statusToReturn = "";
final String mbeanObjectNameStr = "example:type=Status2";
try
{
final ObjectName objectName = new ObjectName(mbeanObjectNameStr);
final StatusMXBean statusBeanProxy =
JMX.newMXBeanProxy(mbsc, objectName, StatusMXBean.class);
statusToReturn = statusBeanProxy.getStatus().toString();
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println( mbeanObjectNameStr + " is a bad object name.:\n"
+ badObjectName.getMessage() );
}

return statusToReturn;
}

public static void main(final String[] arguments)
{
final String jmxUrlString =
"service:jmx:rmi:///jndi/rmi://127.0.0.1:1199/jmxrmi";
try
{
final JMXServiceURL jmxUrl = new JMXServiceURL(jmxUrlString);
final JMXConnector connector = JMXConnectorFactory.connect(jmxUrl);
final MBeanServerConnection mbsc = connector.getMBeanServerConnection();
System.out.println( "MBean reports status: "
+ getServerStatusViaMBean(mbsc) );
System.out.println( "MXBean reports status: "
+ getServerStatusViaMXBean(mbsc) );
}
catch (MalformedURLException badJmxUrl)
{
System.err.println( "ERROR trying to build JMXServiceURL with "
+ jmxUrlString + ":\n" + badJmxUrl.getMessage() );
}
catch (IOException ioEx)
{
System.err.println( "ERROR trying to connect.\n" + ioEx.getMessage() );
}
}
}


One of the more interesting highlights of the custom client is the use of the methods JMX.newMBeanProxy and JMX.newMXBeanProxy. As these names imply, there are versions of these methods for creating proxies on the client side for both standard MBeans and MXBeans. Underneath the covers, these methods seem to turn around and call the MBeanServerInvocationHandler.newProxyInstance method that I used for client-side proxy support in a previous blog entry. While the MBeanServerInvocationHandler.newProxyInstance approach works just fine, the JMX.newMBeanProxy approach tends to be a little easier to use and is a nice Java SE 6 addition to JMX.

With some knowledge of the MBean interface available on the client side, both the Standard MBean and the MXBean work well. This is demonstrated in the next screen snapshot which shows the results of running the custom client and seeing the correct returned values for both the Standard MBean and the MXBean.



The JMX Best Practices document recommends using the Standard MBean interface where possible because of its increased usability (including easy Javadoc support and client-side proxies). While this JMX Best Practices document does reference MXBeans, it was written before they were available for custom MXBeans and it is probably safe to say that MXBeans could be substituted for Standard MBeans in any recommendation regarding use of Standard MBeans. In other words, because MXBeans provide the same usability advantages as Standard MBeans with the additional client-size data type flexibility, developers using Java SE 6 or later will likely want to consider the MXBean as their first option when writing new JMX MBeans.

Java SE 6 built on J2SE 5's JMX support and added several nice new features. Some of these that were highlighted in this blog entry include support for custom MXBeans and the introduction of JMX.newMBeanProxy. Other Java SE 6 advantageous additions include the much-improved JConsole and removal of the need to specify -Dcom.sun.management.jmxremote for local JMX access (thanks to the JMX Attach API). Mandy Chung's blog provides a comprehensive list of Java SE 6 JMX enhancements. MXBeans and other Java SE 6 enhancements to JMX make Java Management Extensions easier than ever to use.

6 comments:

Anonymous said...

Dustin, It's really wonderful and well managed blog on software development. I like it. Keep it up.


Regards,
Ruchi Biswas
Freelance Projects Provider

Seweryn Niemiec said...

Very useful info. Thank you.

@DustinMarx said...

Seweryn,

Thanks for taking the time to leave a comment. It's nice to know it was helpful.

Dustin

Unknown said...

Yet another "thank you" post! I was playing with jmx+scala, and your article came pretty handy while coding my samples!

Unknown said...

Dustin, I'm using a MXBean (not MBean) that has just a simple integer inside, and a get accessor (no set). Yet in JConsole it still shows the value as "Unavailable". Any ideas?

Debmalya Jash said...

Thanks a lot for this blog. It saved my day. It got the exception "does not implement DynamicMBean". Your blog helped me to understand the issue.