Java Dynamic Management Kit 5.0 Tutorial

Chapter 2 Dynamic MBeans

A dynamic MBean implements its management interface programmatically, instead of through static method names. To do this, it relies on metadata classes that represent the attributes and operations exposed for management. Management applications then call generic getters and setters whose implementation must resolve the attribute or operation name to its intended behavior.

One advantage of this instrumentation is that you can use it to make an existing resource manageable quickly. The implementation of the DynamicMBean interface provides an instrumentation wrapper for an existing resource.

Another advantage is that the metadata classes for the management interface can provide human-readable descriptions of the attributes, operations, and the MBean itself. This information could be displayed to a user on a management console to describe how to interact with this particular resource.

The code samples in this chapter are from the files in the DynamicMBean example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

Exposing the Management Interface

In the standard MBean, attributes and operations are exposed statically in the names of methods in the MBean interface. Dynamic MBeans all share the same interface that defines generic methods to access attributes and operations. Because the management interface is no longer visible through introspection, dynamic MBeans must also provide a description of their attributes and operations explicitly.

DynamicMBean Interface

The DynamicMBean class is a Java interface defined by the JMX specification. It specifies the methods that a resource implemented as a dynamic MBean must provide to expose its management interface. Example 2–1 shows the uncommented code for the DynamicMBean interface:


Example 2–1 The DynamicMBean Interface

public interface DynamicMBean {

    public Object getAttribute(String attribute) throws
        AttributeNotFoundException, MBeanException, ReflectionException; 

    public void setAttribute(Attribute attribute) throws
        AttributeNotFoundException, InvalidAttributeValueException,
        MBeanException, ReflectionException ; 

    public AttributeList getAttributes(String[] attributes);

    public AttributeList setAttributes(AttributeList attributes);

    public Object invoke(
        String actionName, Object params[], String signature[])
        throws MBeanException, ReflectionException ;

    public MBeanInfo getMBeanInfo();
 }

The getMBeanInfo method provides a description of the MBean's management interface. This method returns an MBeanInfo object that contains the metadata information about attributes and operations.

The attribute getters and setters are generic, since they take the name of the attribute that needs to be read or written. For convenience, dynamic MBeans must also define bulk getters and setters to operate on any number of attributes at once. These methods use the Attribute and AttributeList classes to represent attribute name-value pairs and lists of name-value pairs, respectively.

Because the names of the attributes are not revealed until runtime, the getters and setters are necessarily generic. In the same way, the invoke method takes the name of an operation and its signature, in order to invoke any method that might be exposed.

As a consequence of implementing generic getters, setters, and invokers, the code for a dynamic MBean is more complex than for a standard MBean. For example, instead of calling a specific getter by name, the generic getter must verify the attribute name and then encode the functionality to read each of the possible attributes.

MBean Metadata Classes

A dynamic MBean has the burden of building the description of its own management interface. The JMX specification defines the Java objects used to completely describe the management interface of an MBean. Dynamic MBeans use these objects to provide a complete self description as returned by the getMBeanInfo method. Agents also use these classes to describe a standard MBean after it has been introspected.

As a group, these classes are referred to as the MBean metadata classes because they provide information about the MBean. This information includes the attributes and operations of the management interface, also the list of constructors for the MBean class, and the notifications that the MBean might send. Notifications are event messages that are defined by the JMX architecture. See Chapter 9, Notification Mechanism.

Each element is described by its metadata object containing its name, a description string, and its characteristics. For example, an attribute has a type and is readable and/or writable. Table 2–1 lists all MBean metadata classes:

Table 2–1 MBean Metadata Classes

Class Name 

Purpose 

MBeanInfo

Top-level object containing arrays of metadata objects for all MBean elements; also includes the name of the MBean's Java class and a description string 

MBeanFeatureInfo

Parent class from which all other metadata objects inherit a name and a description string 

MBeanOperationInfo

Describes an operation: the return type, the signature as an array of parameters, and the impact (whether the operation just returns information or modifies the resource) 

MBeanConstructorInfo

Describes a constructor by its signature 

MBeanParameterInfo

Gives the type of a parameter in an operation or constructor signature 

MBeanAttributeInfo

Describes an attribute: its type, whether it is readable, and whether it is writable 

MBeanNotificationInfo

Contains an array of notification type strings 

Implementing a Dynamic MBean

A dynamic MBean consists of a class that implements the DynamicMBean interface coherently. By this, we mean a class that exposes a management interface whose description matches the attributes and operations that are accessible through the generic getters, setters and invokers.

A dynamic MBean class can declare any number of public or private methods and variables. None of these are visible to management applications. Only the methods implementing the DynamicMBean interface are exposed for management. A dynamic MBean can also rely on other classes that might be a part of the manageable resource.

Dynamic Programming Issues

An MBean is a manageable resource that exposes a specific management interface. The name dynamic MBean refers to the fact that the interface is revealed at runtime, instead of through the introspection of static class names. The term dynamic is not meant to imply that the MBean can dynamically change its management interface.

getMBeanInfo Method

Because the MBean description should never change, it is usually created one time only at instantiation, and the getMBeanInfo method simply returns its reference at every call. The MBean constructor should therefore build the MBeanInfo object from the MBean metadata classes such that it accurately describes the management interface. And since most dynamic MBeans will always be instantiated with the same management interface, building the MBeanInfo object is fairly straightforward.

Example 2–2 shows how the SimpleDynamic MBean defines its management interface, as built at instantiation and returned by its getMBeanInfo method:


Example 2–2 Implementation of the getMBeanInfo Method

// class constructor
public SimpleDynamic() {

    buildDynamicMBeanInfo();
}

// internal variables describing the MBean
private String dClassName = this.getClass().getName();
private String dDescription = "Simple implementation of a dynamic MBean.";

// internal variables for describing MBean elements
private MBeanAttributeInfo[] dAttributes = new MBeanAttributeInfo[2];
private MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
private MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
private MBeanInfo dMBeanInfo = null;

// internal method
private void buildDynamicMBeanInfo() {

    dAttributes[0] = new MBeanAttributeInfo(
        "State",                 // name
        "java.lang.String",      // type
        "State: state string.",  // description
        true,                    // readable
        true);                   // writable
    dAttributes[1] = new MBeanAttributeInfo(
        "NbChanges",
        "java.lang.Integer",
        "NbChanges: number of times the State string has been changed.",
        true,
        false);

    // use reflection to get constructor signatures
    Constructor[] constructors = this.getClass().getConstructors();
    dConstructors[0] = new MBeanConstructorInfo(
        "SimpleDynamic(): No-parameter constructor",  //description
        constructors[0]);                  // the contructor object

    MBeanParameterInfo[] params = null;
    dOperations[0] = new MBeanOperationInfo(
        "reset",                     // name
        "Resets State and NbChanges attributes to their initial values",
                                     // description
        params,                      // parameter types
        "void",                      // return type
        MBeanOperationInfo.ACTION);  // impact

    dMBeanInfo = new MBeanInfo(dClassName,
                               dDescription,
                               dAttributes,
                               dConstructors,
                               dOperations,
                               new MBeanNotificationInfo[0]);
}

// exposed method implementing the DynamicMBean.getMBeanInfo interface
public MBeanInfo getMBeanInfo() {

    // return the information we want to expose for management:
    // the dMBeanInfo private field has been built at instantiation time,
    return(dMBeanInfo);
}

Generic Attribute Getters and Setters

Generic getters and setters take a parameter that indicates the name of the attribute to read or write. There are two issues to keep in mind when implementing these methods:

The getAttribute method is the simplest, since only the attribute name must be verified:


Example 2–3 Implementation of the getAttribute Method

public Object getAttribute(String attribute_name) 
    throws AttributeNotFoundException,
           MBeanException,
           ReflectionException {

    // Check attribute_name to avoid NullPointerException later on
    if (attribute_name == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException("Attribute name cannot be null"), 
            "Cannot invoke a getter of " + dClassName +
                " with null attribute name");
    }

    // Call the corresponding getter for a recognized attribute_name
    if (attribute_name.equals("State")) {
        return getState();
    } 
    if (attribute_name.equals("NbChanges")) {
        return getNbChanges();
    }

    // If attribute_name has not been recognized
    throw(new AttributeNotFoundException(
        "Cannot find " + attribute_name + " attribute in " + dClassName));
}

// internal methods for getting attributes
public String getState() {
    return state;
}

public Integer getNbChanges() {
    return new Integer(nbChanges);
}

// internal variables representing attributes
private String      state = "initial state";
private int         nbChanges = 0;

The setAttribute method is more complicated, since you must also ensure that the given type can be assigned to the attribute and handle the special case for a null value:


Example 2–4 Implementation of the setAttribute Method

public void setAttribute(Attribute attribute)
    throws AttributeNotFoundException,
           InvalidAttributeValueException,
           MBeanException, 
           ReflectionException {

    // Check attribute to avoid NullPointerException later on
    if (attribute == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException("Attribute cannot be null"),
            "Cannot invoke a setter of " + dClassName +
                " with null attribute");
    }
    // Note: Attribute class constructor ensures the name not null
    String name = attribute.getName();
    Object value = attribute.getValue();

    // Call the corresponding setter for a recognized attribute name
    if (name.equals("State")) {
        // if null value, try and see if the setter returns any exception
        if (value == null) {
            try {
                setState( null );
            } catch (Exception e) {
                throw(new InvalidAttributeValueException(
                    "Cannot set attribute "+ name +" to null")); 
            }
        }
        // if non null value, make sure it is assignable to the attribute
        else {
            try {
                if ((Class.forName("java.lang.String")).isAssignableFrom(
                        value.getClass())) {
                    setState((String) value);
                }
                else {
                    throw(new InvalidAttributeValueException(
                        "Cannot set attribute "+ name +
                            " to a " + value.getClass().getName() +
                            " object, String expected"));
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    // optional: recognize an attempt to set a read-only attribute
    else if (name.equals("NbChanges")) {
        throw(new AttributeNotFoundException(
            "Cannot set attribute "+ name +
                " because it is read-only"));
    }

    // unrecognized attribute name
    else {
        throw(new AttributeNotFoundException(
            "Attribute " + name + " not found in " +
                this.getClass().getName()));
    }
}

// internal method for setting attribute
public void setState(String s) {
    state = s;
    nbChanges++;
}

Notice that if a change in your management solution requires you to change your management interface, it will be harder to do with a dynamic MBean. In a standard MBean, each attribute and operation is a separate method, so unchanged attributes are unaffected. In a dynamic MBean, you must modify the generic methods that encode all attributes.

Bulk Getters and Setters

The DynamicMBean interface includes bulk getter and setter methods for reading or writing more than one attribute at once. These methods rely on the classes shown in Table 2–2.

Table 2–2 Bulk Getter and Setter Classes

Class Name 

Description 

Attribute

A simple object that contains the name string and value object of any attribute 

AttributeList

A dynamically extendable list of Attribute objects (extends java.util.ArrayList)

The AttributeList class extends the java.util.ArrayList class.

The bulk getter and setter methods usually rely on the generic getter and setter, respectively. This makes them independent of the management interface, which can simplify certain modifications. In this case, their implementation consists mostly of error checking on the list of attributes. However, all bulk getters and setters must be implemented so that an error on any one attribute does not interrupt or invalidate the bulk operation on the other attributes.

If an attribute cannot be read, then its name-value pair is not included in the list of results. If an attribute cannot be written, it will not be copied to the returned list of successful set operations. As a result, if there are any errors, the lists returned by bulk operators will not have the same length as the array or list passed to them. In any case, the bulk operators do not guarantee that their returned lists have the same ordering of attributes as the input array or list.

The SimpleDynamic MBean shows one way of implementing the bulk getter and setter methods:


Example 2–5 Implementation of the Bulk Getter and Setter Methods

public AttributeList getAttributes(String[] attributeNames) {

    // Check attributeNames to avoid NullPointerException later on
    if (attributeNames == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException(
                "attributeNames[] cannot be null"),
            "Cannot invoke a getter of " + dClassName);
    }
    AttributeList resultList = new AttributeList();

    // if attributeNames is empty, return an empty result list
    if (attributeNames.length == 0)
            return resultList;
        
    // build the result attribute list
    for (int i=0 ; i<attributeNames.length ; i++){
        try {        
            Object value = getAttribute((String) attributeNames[i]);     
            resultList.add(new Attribute(attributeNames[i],value));
        } catch (Exception e) {
            // print debug info but continue processing list
            e.printStackTrace();
        }
    }
    return(resultList);
}

public AttributeList setAttributes(AttributeList attributes) {

    // Check attributesto avoid NullPointerException later on
    if (attributes == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException(
                "AttributeList attributes cannot be null"),
            "Cannot invoke a setter of " + dClassName);
    }
    AttributeList resultList = new AttributeList();

    // if attributeNames is empty, nothing more to do
    if (attributes.isEmpty())
        return resultList;

    // try to set each attribute and add to result list if successful
    for (Iterator i = attributes.iterator(); i.hasNext();) {
        Attribute attr = (Attribute) i.next();
        try {
            setAttribute(attr);
            String name = attr.getName();
            Object value = getAttribute(name); 
            resultList.add(new Attribute(name,value));
        } catch(Exception e) {
            // print debug info but keep processing list
            e.printStackTrace();
        }
    }
    return(resultList);
}

Generic Operation Invoker

A dynamic MBean must implement the invoke method so that operations in the management interface can be called. This method requires the same considerations as the generic getter and setter:

The implementation in the SimpleDynamic MBean is relatively simple due to the one operation with no parameters:


Example 2–6 Implementation of the invoke Method

public Object invoke(
        String operationName, Object params[], String signature[])
    throws MBeanException, ReflectionException {

    // Check operationName to avoid NullPointerException later on
    if (operationName == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException(
                "Operation name cannot be null"),
            "Cannot invoke a null operation in " + dClassName);
    }

    // Call the corresponding operation for a recognized name
    if (operationName.equals("reset")){
        // this code is specific to the internal "reset" method:
        reset();     // no parameters to check
        return null; // and no return value
    } else { 
        // unrecognized operation name:
        throw new ReflectionException(
            new NoSuchMethodException(operationName), 
            "Cannot find the operation " + operationName +
                " in " + dClassName);
    }
}

// internal variable
private int         nbResets = 0;

// internal method for implementing the reset operation
public void reset() {
    state = "initial state";
    nbChanges = 0;
    nbResets++;
}

// Method not revealed in the MBean description and not accessible
// through "invoke" therefore it is only available for internal mgmt
public Integer getNbResets() {
    return new Integer(nbResets);
}

As it is written, the SimpleDynamic MBean correctly provides a description of its management interface and implements its attributes and operations. However, this example demonstrates the need for a strict coherence between what is exposed by the getMBeanInfo method and what can be accessed through the generic getters, setters, and invoker.

A dynamic MBean whose getMBeanInfo method describes an attribute or operation that cannot be accessed is not compliant with the JMX specification and is technically not a manageable resource. Similarly, a class could make attributes or operations accessible without describing them in the returned MBeanInfo object. Since MBeans should raise an exception when an undefined attribute or operation is accessed, this would, again, technically not be a compliant resource.

Running the Dynamic MBean Example

The examplesDir/DynamicMBean directory contains the SimpleDynamic.java file that makes up the MBean. The DynamicMBean interface is defined in the javax.management package provided in the runtime JAR file (jdmkrt.jar) of the Java Dynamic Management Kit (DMK). This directory also contains a simple agent application that instantiates this MBean, calls its getMBeanInfo method to get its management interface, and manipulates its attributes and operations.

To Run the Dynamic MBean Example
  1. Compile all files in this directory with the javac command.

    For example, on the Solaris platform, type:


    $ cd examplesDir/DynamicMBean/
    $ javac -classpath classpath *.java
    
  2. To run the example, start the agent class that will interact with the SimpleDynamic MBean:


    $ java -classpath classpath DynamicAgent
    
  3. Press Enter when the application pauses, to step through the example.

    The agent application handles all input and output in this example and gives a view of the MBean at runtime.

This example demonstrates how the management interface encoded in the getMBeanInfo method is made visible in the agent application. We can then see the result of calling the generic getters and setters and the invoke method. Finally, the code for filtering attribute and operation errors is exercised, and we see the exceptions from the code samples as they are raised at runtime.

Comparison With the SimpleStandard Example

Now that we have implemented both types of MBeans, we can compare how they are managed. We intentionally created a dynamic MBean and a standard MBean with the same management interface so that we can do exactly the same operations on them. On the Solaris platform, we can compare the relevant code of the two agent applications with the diff utility (your output might vary):


$ cd examplesDir
$ diff ./StandardMBean/StandardAgent.java ./DynamicMBean/DynamicAgent.java
[...]
41c40
< public class StandardAgent {
---
> public class DynamicAgent {
49c48
<     public StandardAgent() {
---
>     public DynamicAgent() {
77c76
<       StandardAgent agent = new StandardAgent();
---
>       DynamicAgent agent = new DynamicAgent();
88c87
<       echo("\n>>> END of the SimpleStandard example:\n");
---
>       echo("\n>>> END of the SimpleDynamic example:\n");
113c112
<       String mbeanName = "SimpleStandard";
---
>       String mbeanName = "SimpleDynamic";

If the two agent classes had the same name, the only programmatic difference would be the following:


113c112
<       String mbeanName = "SimpleStandard";
---
>       String mbeanName = "SimpleDynamic";

We can see that the only difference between the two example agents handling different types of MBeans is the name of the MBean class that is instantiated. In other words, standard and dynamic MBeans are indistinguishable from the agent's point of view. The JMX architecture enables managers to interact with the attributes and operations of a manageable resource, and the specification of the agent hides any implementation differences between MBeans.

Because we know that the two MBeans are being managed identically, we can also compare their runtime behavior. In doing so, we can draw two conclusions:

Dynamic MBean Execution Time

In the introduction to this chapter we presented two structural advantages of dynamic MBeans, namely the ability to wrap existing code to make it manageable and the ability to provide a self-description of the MBean and its features. Another advantage is that using dynamic MBeans can lead to faster overall execution time.

The performance gain depends on the nature of the MBean and how it is managed in the agent. For example, the SimpleDynamic MBean, as it is used, is probably not measurably faster than the SimpleStandard example in Chapter 1, Standard MBeans. When seeking improved performance, there are two situations that must be considered:

Because the dynamic MBean provides its own description, the agent does not need to introspect it as it would a standard MBean. Since introspection is done only once by the agent, this is a one-time performance gain during the lifetime of the MBean. In an environment where there are many MBean creations and where MBeans have a short lifetime, a slight performance increase can be measured.

However, the largest performance gain is in the management operations, when calling the getters, setters and invoker. As we shall see in Part II, the agent makes MBeans manageable through generic getters, setters, and invokers. In the case of standard MBeans, the agent must do the computations for resolving attribute and operation names according to the design patterns. Because dynamic MBeans necessarily expose the same generic methods, these are called directly by the agent. When a dynamic MBean has a simple management interface requiring simple programming logic in its generic methods, its implementation can show a better performance than the same functionality in a standard MBean.