Utilities

MX4J provides some utilities to ease MBean development and use.
These utility classes may be extracted and re-jarred for use with another JMX implementation, different from MX4J; otherwise they're shipped by default with the MX4J JMX implementation.

Proxy for MBeanServer invocations on standard MBeans

Sometimes it is very tedious calling the MBeanServer.invoke method, since the invocation is very verbose and since you have to deal with exception handling that is not directly related to the method you want to call on the MBean.

Take a look at this example, taken from MX4J implementation of the persistence for MBeans:

Example 2.2. Typical MBeanServer.invoke invocation

		
public interface PersisterMBean
{
	public void store(Object data) throws MBeanException, RuntimeOperationsException, InstanceNotFoundException;
	...
}

public abstract class Persister implements PersisterMBean {}

public class MBeanPersister extends Persister
{
	private MBeanServer m_server;
	private ObjectName m_name;

	public MBeanPersister(MBeanServer server, ObjectName name)
	{
		m_server = server;
		m_name = name;
	}
	public void store(Object data) throws MBeanException, RuntimeOperationsException, InstanceNotFoundException
	{
		try
		{
			m_server.invoke(m_name, "store", new Object[] {data}, new String[] {"java.lang.Object"});
		}
		catch (ReflectionException x)
		{
			throw new MBeanException(x.getTargetException());
		}
	}
}
		
		

As you can see, the store implementation uses the MBeanServer.invoke call that is error-prone, and forces you to catch and rethrow exceptions that are not related to the invocation of the store method.

However, thanks to the dynamic proxy API introduced in J2SE version 1.3, it is possible to avoid all the problems we mentioned above, using the mx4j.util.StandardMBeanProxy class. Take a look at the example below and compare the store implementation to see how useful this utility class is:

Example 2.3. Proxy MBeanServer invocation

		
public class MBeanPersister extends Persister
{
	private MBeanServer m_server;
	private ObjectName m_name;
	private PersisterMBean m_proxy;

	public MBeanPersister(MBeanServer server, ObjectName name)
	{
		m_server = server;
		m_name = name;
		m_proxy = (PersisterMBean)StandardMBeanProxy.create(PersisterMBean.class, server, name);
	}
	public void store(Object data) throws MBeanException, RuntimeOperationsException, InstanceNotFoundException
	{
		m_proxy.store(data);
	}
}
		
		

As you can see, the invocation is now type-safe so that possible errors are caught by the compiler, and there is no need of extra exception handling.

The StandardMBeanProxy class works with standard MBeans, since you have to specify the management MBean interface to the StandardMBeanProxy.create and cast the returned object to the same interface. You should not call methods inherited from java.lang.Object, but only methods belonging to the management MBean interface.

AbstractDynamicMBean base class for DynamicMBean implementation

Writing an MBean by implementing the javax.management.DynamicMBean interface can be a heavy and tedious task.

MX4J provides the class mx4j.AbstractDynamicMBean as a base class to implement a dynamic MBean.
This class handles most of the tedious work that must be done when implementing dynamic MBeans, so that the MBean implementor has just to override few methods to provide the needed information required to create the MBean.

The methods of the AbstractDynamicMBean class can be divided in 2 groups: the methods of the DynamicMBean interface and the methods added by AbstractDynamicMBean itself.

AbstractDynamicMBean already implements all the methods of the DynamicMBean interface, and normally the MBean implementor does not have to override them.
The methods belonging to the second group are normally overridden by the MBean implementor to provide the MBean metadata information, and are the following:

  • createMBeanAttributeInfo, if the MBeans has manageable attributes
  • createMBeanOperationInfo, if the MBeans has manageable operations
  • createMBeanNotificationInfo, if the MBeans has manageable notifications
  • createMBeanConstructorInfo, if the MBeans has manageable constructors
  • getMBeanDescription

A third group of methods belongs to the subclass of AbstractDynamicMBean and are the implementation methods, the ones that implement the functionality of the MBean itself (see below for an example).

Example 2.4. Subclassing AbstractDynamicMBean

		
public class SimpleDynamic extends AbstractDynamicMBean
{
   /* Method of the second group that is overridden */
   protected MBeanAttributeInfo[] createMBeanAttributeInfo()
   {
      return new MBeanAttributeInfo[]
      {
         new MBeanAttributeInfo("Name", String.class.getName(), "The name", true, true, false)
      };
   }

   /* Method of the second group that is overridden */
   protected String getMBeanDescription()
   {
      return "A simple DynamicMBean";
   }

   /* Method of the third group that implements the MBean functionality */
   public String getName() { ... }

   /* Method of the third group that implements the MBean functionality */
   public void setName(String name) { ... }
}
		
		

As you can see above, no methods from the DynamicMBean interface needs to be implemented. It is sufficient to override some (or all) of the methods of the second group, and provide the relative methods of the third group.

Normally the MBean implementor extends AbstractDynamicMBean, but if the MBean already extends another class it is sufficient to implement DynamicMBean and delegate to a subclass of AbstractDynamicMBean, having care of calling the setResource method (see example below).

Example 2.5. Delegating to AbstractDynamicMBean subclass

		
public class ComposedDynamic extends MyBaseClass implements DynamicMBean
{
   /* Create an AbstractDynamicMBean subclass */
   private AbstractDynamicMBean delegate = new AbstractDynamicMBean()
   {
      protected MBeanAttributeInfo[] createMBeanAttributeInfo()
      {
         return new MBeanAttributeInfo[]
         {
            new MBeanAttributeInfo("Status", int.class.getName(), "The status", true, true, false),
            new MBeanAttributeInfo("Enabled", boolean.class.getName(), "The enable status", true, false, true)
         };
      }

      protected MBeanOperationInfo[] createMBeanOperationInfo()
      {
         return new MBeanOperationInfo[]
         {
            new MBeanOperationInfo("enable", "Enables this MBean", new MBeanParameterInfo[0], Void.class.getName(), MBeanOperationInfo.ACTION),
            new MBeanOperationInfo("disable", "Disables this MBean", new MBeanParameterInfo[0], Void.class.getName(), MBeanOperationInfo.ACTION)
         };
      }
   };

   private int status;
   private boolean enabled;

   public ComposedDynamicMBean()
   {
      // Set the actual resource
      delegate.setResource(this);
   }

   /* Implement the methods of DynamicMBean interface to delegate to the AbstractDynamicMBean subclass */

   public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException
   {
      return delegate.getAttribute(attribute);
   }

   // Do the same with all other methods of DynamicMBean interface
   ...

   /* Methods of the third group that implements MBean functionality */

   public void setStatus(int status)
   {
      this.status = status;
   }

   public int getStatus()
   {
      return status;
   }

   public boolean isEnabled()
   {
      return this.enabled;
   }

   public void enable()
   {
      this.enabled = true;
   }

   public void disable()
   {
      this.enabled = false;
   }
}
		
		

AbstractDynamicMBean can also be used for non-invasive management: if you already have a component but you don't want to change it to implement a management interface, you can set it as target of a subclass of AbstractDynamicMBean and provide the suitable metadata information.

Example 2.6. Subclassing AbstractDynamicMBean

		
public class NonInvasiveDynamic extends AbstractDynamicMBean
{
   /* Methods of the second group that are overridden */
   protected MBeanOperationInfo[] createMBeanOperationInfo()
   {
      return new MBeanOperationInfo[]
      {
         new MBeanOperationInfo("start", "Starts this MBean", new MBeanParameterInfo[0], Void.class.getName(), MBeanOperationInfo.ACTION),
         new MBeanOperationInfo("stop", "Stops this MBean", new MBeanParameterInfo[0], Void.class.getName(), MBeanOperationInfo.ACTION)
      };
   }

   protected String getMBeanDescription()
   {
      return "A non invasive DynamicMBean that manages resource";
   }

   /* Constructor that takes the managed resource */
   public NonInvasiveDynamic(ExternalService service)
   {
      // Set the actual resource that this MBean represents.
      setresource(service);
   }

/* Old main, before JMX
   public static void main(String[] args) throws Exception
   {
      // Create the service
      ExternalService service = new ExternalService();

      // Start the service
      service.start();
   }
*/
   public static void main(String[] args) throws Exception
   {
      // Create the service
      ExternalService service = new ExternalService();

      MBeanServer server = MBeanServerFactory.createMBeanServer();
      NonInvasiveDynamic mbean = new NonInvasiveDynamic(service);
      ObjectName name = new ObjectName("domain:key=value");
      server.registerMBean(mbean, name);

      // Now start the service via JMX:
      // Few lines more, but now the service is manageable !
      server.invoke(name, "start", null, null);
   }
}
		
		

The example above shows how simple can be to plug JMX into already existing architectures, and how it is possible, in few lines of code, to make services manageable (and remotely manageable with JSR 160) without even impacting already existing service's code.