IN THIS CHAPTER
Dynamic MBeans, as the name implies, provide a more dynamic definition of the management interface compared to Standard MBeans. Instead of relying on a statically typed Java interface, the Dynamic MBeans implement a generic interface that provides methods for the agent to discover the management interface. This allows the management interface to be changed without a recompilation of the class files and enables the management attributes and operations to be created dynamically at runtime.
Due to their dynamic nature, Dynamic MBeans are quite useful for situations in which you anticipate that the management interface will change often. In addition, the Dynamic MBeans enable you to define the management interface declaratively, for example in an XML file, and create the management interface at runtime. If the exposed management interface is determined depending on an external information source, you might want to postpone the creation of the management interface until such information is available.
One possible case for using Dynamic MBeans is enabling the Java management on existing resources or classes that do not follow the Standard MBean naming conventions. Dynamic MBeans lend themselves to act as adapters exposing a consistent management view to the agent level and delegating the agent's requests to correct methods or even to entirely different components not directly seen by the agent.
Another aspect of the Dynamic MBeans is that they expose the metadata that describes the management interface to the developer. With Standard MBeans, the metadata is generated by the agent that used introspection to discover the management interface. With Dynamic MBeans, the developer is responsible for providing this metadata.
In this chapter, you will first look at all the details of how to implement the methods of the DynamicMBean
interface. Then you will go through the metadata structures and see what type of information the developer needs to provide with each Dynamic MBean. In the end, you'll build the equivalent of the standard User MBean as a Dynamic MBean and then see how the JMX management architecture allows you to use the different implementations interchangeably.
To be recognized as a Dynamic MBean by the agent, the MBean must implement the DynamicMBean
interface. The agent will use the methods defined in this interface to interact with the MBean to determine the management attributes and operations of the MBean, to query the attribute values, and to invoke the MBean operations. The DynamicMBean
interface contains six methods (Listing 4.1) that you, as a developer, are responsible for implementing. The methods described in the interface are generic and detyped, so some extra discipline is in order when implementing Dynamic MBeans. The generic definition of the Dynamic MBean interface provides increased flexibility but should be handled with care.
The DynamicMBean
interface is shown in Listing 4.1 (the exceptions declared in the interface have been left out for the sake of brevity).
Example 4.1. DynamicMBean interface
package javax.management; public interface DynamicMBean { public MBeanInfo getMBeanInfo(); public Object getAttribute(String attribute); public AttributeList getAttributes(String[] attributes); public void setAttribute(Attribute attribute); public AttributeList setAttributes(AttributeList attributes); public Object invoke(String actionName, Object[] params, String[] signature); }
Notice the declaration of the invoke()
method. All the type information is passed as strings, forming the invocation's signature. The params
and signature
arrays must be of the same length and a parameters type is retrieved from the same index location in the corresponding signature array.
The getMBeanInfo()
method returns an object that fully describes the management interface of the Dynamic MBean. This includes the MBean's attributes, operations, constructors, and notifications. Each of the metadata elements has its own class in the JMX API, which you will examine in detail later in this chapter, in the section “MBean Metadata Classes.”
The getAttribute()
and getAttributes()
methods are used by the agent to retrieve the values of the management attributes from the Dynamic MBean. When you create a Dynamic MBean, it is up to you to map the named attributes to their corresponding values in the MBean implementation and then return these values to the agent on request. The full declaration of the getAttribute()
method is as follows:
public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException;
The returned object must contain the value of the attribute. The runtime type of the returned object must also match the Java type defined in the Dynamic MBean's metadata classes. You can use the AttributeNotFoundException
class to indicate to the agent that the named attribute was not found or its value could not be retrieved. The MBeanException
class can be used for wrapping any checked exceptions that may be thrown by the getAttribute()
implementation, such as the java.io.IOException
. The ReflectionException
class can be used to propagate exceptions resulting from the use of the java.lang.reflect
package back to the agent. You will learn more about the JMX agent exception mechanism and each declared exception in Chapter 6, “MBean Server.”
Listing 4.2 is an example implementation of the getAttribute()
method that processes the attributes of the User MBean you built as an example for Standard MBeans. This code snippet shows how you would deal with readable attributes for dynamic User MBean.
Example 4.2. DynamicUser.java
...
public Object getAttribute(String attribute) throws
AttributeNotFoundException,
MBeanException,
ReflectionException {
if (attribute == null || attribute.equals(""))
throw new IllegalArgumentException(
"null or empty attribute name"
);
// map the named attributes to fields
if (attribute.equals("Name"))
return name;
if (attribute.equals("Address"))
return address;
if (attribute.equals("PhoneNumbers"))
return numbers;
if (attribute.equals("ID"))
return new Long(id);
throw new AttributeNotFoundException(
"Attribute " + attribute + " not found."
);
}
...
In Listing 4.2, you first check the attribute parameter for a null
reference and empty strings. An attempt to retrieve an attribute with a null
or empty name will yield an IllegalArgumentException
being thrown from the implementation. The agent will catch this and other runtime exceptions and will wrap them in a subclass of javax.management.JMRuntimeException
.
If the given attribute parameter is valid, a simple comparison for the parameter string is made next. Remember that the MBean attribute names are case sensitive, so don't confuse the MBean attribute names with the field names in the implementation class. In a more robust implementation than that shown here, it would be a good idea to define the attribute names as constants to eliminate the possibility of simple typing errors.
Again, notice that the management attribute names can be independent of the underlying implementation names. You can use names in the management interface that are more self-explanatory and consistent than the names used in the resource implementation. With Dynamic MBeans it is up to you to map the attribute name to the object field names.
The getAttributes()
method returns the values of several MBean attributes. The return type is an AttributeList
class defined in the JMX API, which is a subclass of the ArrayList
class in the Java Collections framework. The returned list must contain a set of Attribute
objects that hold the name and the value of the requested attribute.
The getAttributes()
method does not signal errors using the Java exception mechanism. Instead, any attributes whose value retrieval caused an error condition should be left out of the return list.
The setAttribute()
and setAttributes()
methods are the corresponding setter methods to the getter methods in the Dynamic MBean interface. Again, setAttribute()
is used for setting a single MBean attribute value, and it is up to the MBean implementation to map the given attribute name in the implementation and set the attribute value. The setAttribute()
method uses the Attribute
class as a wrapper for passing the attribute name and value. The full declaration of the setAttribute()
method defines several exceptions that can be used for signaling error conditions.
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException;
The AttributeNotFoundException
class can be used to indicate if the given attribute does not exist in the MBean. InvalidAttributeValueException
should be used to indicate the value given in the Attribute
object is not acceptable for the given attribute or the value is of the wrong type. The use of MBeanException
class is similar to that seen with the getAttribute()
method. If the attribute-set operation causes a checked exception to be thrown in the MBean implementation, it can be caught, wrapped into an MBeanException
object, and rethrown to be managed by the agent. Similarly, the ReflectionException
class can be used to wrap exceptions resulting from using the operations of java.lang.reflect
package in setting the MBean's state.
Listing 4.3 is an example implementation of the setAttribute()
method. It implements the attributes for the example User MBean you built as a Standard MBean example and shows how similar functionality is achieved with Dynamic MBeans. Each attribute name is matched individually and cast to String
or to an array of String
objects. In case there is a type mismatch between the provided attribute type and the expected type, the resulting ClassCastException
is caught and rethrown as an InvalidAttributeValueException
.
Example 4.3. DynamicUser.java
... public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { if (attribute == null) throw new AttributeNotFoundException("null attribute"); // map the attributes to fields try { if (attribute.getName().equals("Name")) this.name = (String)attribute.getValue(); else if (attribute.getName().equals("Address")) this.address = (String)attribute.getValue(); else if (attribute.getName().equals("PhoneNumbers")) this.numbers = (String[])attribute.getValue(); else if (attribute.getName().equals("Password")) this.passwd = (String)attribute.getValue(); else throw new AttributeNotFoundException( "attribute "+attribute.getName()+" not found." ); } catch (ClassCastException e) { // In case of the type mismatch, throw an // InvalidAttributeValueException throw new InvalidAttributeValueException( "Invalid attribute type " + attribute.getValue().getClass().getName() ); } } ...
The setAttributes()
method takes an AttributeList
as a parameter and allows for the setting of several MBean attributes at once. Similar to getAttributes()
, this method does not indicate error conditions using the exception mechanism but returns a list of attributes whose values were successfully set. In case there were errors in setting some of the attributes, they will not be added to the returned result set. You can quickly determine if all attributes were successfully set by comparing the sizes of the input list and return list.
The invoke()
method is called by the agent when a request to invoke a management operation has been made. The parameters passed with the call include the operation's name, an array of objects containing the values for the operation parameters, and an array of strings declaring the types of the parameter values. Again, the MBean implementation is responsible for parsing all of this information and mapping the invoke call to the appropriate methods in the implementation. The checked exceptions thrown by the implementation can be passed back to the agent by wrapping them with the MBeanException
class. The return type from the invoke operation must match the type declared in the management operation metadata for this Dynamic MBean.
Listing 4.4 shows a code snippet that handles the management operation invocation of a Dynamic MBean. Notice that each of the management operations are mapped from their names to method calls in the implementation class.
Example 4.4. DynamicUser.java
...
public Object invoke(String actionName, Object[] params,
String[] signature) throws
MBeanException, ReflectionException {
if (actionName == null || actionName.equals(""))
throw new IllegalArgumentException("null operation");
// map management operations
if (actionName.equals("printInfo")
return printInfo();
else if (actionName.equals("addPhoneNumber")) {
addPhoneNumber((String)params[0]);
return null;
}
else if (actionName.equals("removePhoneNumber")) {
removePhoneNumber(((Integer)params[0]).intValue());
return null;
}
else throw new IllegalArgumentException(
"unknown operation " + actionName);
}
...
You have now seen how to implement the basic mechanisms for readable and writable management attributes with Dynamic MBeans and how management operation invocations are implemented. Next, you'll look into the metadata structures of the Dynamic MBeans.
When creating Dynamic MBeans, the metadata needs to be explicitly declared by the programmer. In the case of the Standard MBeans, the metadata was automatically built by the agent via Java language introspection. Now you will see what it is that the agent actually created from the Standard MBean's interface.
The Dynamic MBeans expose their management interface via metadata classes defined in the JMX API. The metadata is retrieved by the agent using the getMBeanInfo()
method of the DynamicMBean
interface. The returned object, an instance of the MBeanInfo
class, is an aggregate of several metadata classes. The metadata classes describe all elements of the MBean's management interface—attributes, operations, constructors, and notifications. In addition, the parameters for operations and constructors are represented by their own metadata class. The corresponding metadata classes are
MBeanAttributeInfo
MBeanOperationInfo
MBeanConstructorInfo
MBeanNotificationInfo
MBeanParameterInfo
Figure 4.1 shows the structure of the metadata classes in the javax.management
package.
As you can see from the figure, the MBeanInfo acts as a collection class for all metadata elements that inherit common features from the MBeanFeatureInfo
class. The MBeanOperationInfo
and MBeanConstructorInfo
classes also use the MBeanParameterInfo
to construct the metadata that describes the signature of these methods.
The MBeanInfo
object is available to the management applications via the getMBeanInfo()
method of the MBeanServer
interface. Through the agent, another MBean or a management application can discover the management interface of an MBean and its metadata description.
The metadata can also be extended to supply additional information to what is described here. You will see an example of how to do this when the Model MBeans are discussed and see how they attach additional information to the metadata that the agent or the management application can utilize.
The MBeanInfo
class fully describes the management interface of an MBean. Using the methods of this class, you can retrieve metadata for the attributes, operations, constructors, and notifications of the MBean.
Table 4.1 lists the methods of the MBeanInfo
class with short descriptions of their use.
Table 4.1. List of the Methods of the MBeanInfo
Class
The metadata classes are available to all clients through the MBeanServer
interface. The metadata can also be extended to provide a richer description of the management interface to the agent and the management applications. You will see an example of this in Chapter 5, “Model MBeans.”
The MBeanFeatureInfo
class is a superclass for all the metadata classes in the javax.management
package, except the MBeanInfo
class. This class provides an access to two fields, the name
and the description
of an MBean metadata object. The subclasses use the name to return the name of the MBean attribute, operation, parameter, constructor, or notification. The description is used to return a human readable string describing the purpose and effects of the MBean metadata element. This is often a string that gives a short description of the management operation or describes the semantics of an attribute, parameter, or a notification. The description string can be used by the management tools to describe the managed resource and its properties to the user.
Notice that when creating Standard MBeans, it is not possible to set the descriptions for the management interface elements. The agent created the metadata structures automatically by introspecting the Standard MBean interface and had no access to any descriptive information. Unfortunately, there isn't a mechanism defined in the JMX specification that would allow the Standard MBeans to relay this type of information to the agent. You need to use Dynamic MBeans or their extensions to be able to provide additional metadata, such as the attribute and operation descriptions.
The MBeanAttributeInfo
class is used to describe the attributes of an MBean. The class contains the information about the attribute's runtime type, name, description, and its access type. The name
is the programmatic name of the management attribute. The description
should be a human readable description of the attribute and its intended use.
The runtime type string must be a fully qualified name of the class, meaning that the package declaration must be included in the classname, such as java.lang.String
. Also note that this rule applies to the Java array types as well as the primitive types. For example, the fully qualified classname of an array of String
objects is "[Ljava.lang.String;"
, and a two-dimensional array of String
objects is "[[Ljava.lang.String;"
. For arrays of primitive types, it gets even hairier. For example, the fully qualified classname of an array of long
types is "[J"
.
Table 4.2 shows examples of some fully qualified class names.
Table 4.2. Some Java Types and Their Fully Qualified Class Names
Type | Fully Qualified Name | Type | Fully Qualified Name |
---|---|---|---|
byte
| “byte” | byte[]
| “[B” |
char
| “char” | char[]
| “[C” |
double
| “double” | double[]
| “[D” |
float
| “float” | float[]
| “[F” |
int
| “int” | int[]
| “[I” |
long
| “long” | long[]
| “[J” |
short
| “short” | short[]
| “[S” |
boolean
| “boolean” | boolean[]
| “[Z” |
Object
| “java.lang.Object” | Object[]
| “[Ljava.lang.Object;” |
void
| “void” | — | — |
More details on the string representation of the classnames can be found in the API documentation of the java.lang.Class.getName()
method.
Because trying to remember the fully qualified names can be difficult and error-prone, especially in the case of arrays, there's a workaround you can use when creating the metadata classes. The Java language specification defines an expression called a class literal. You can use the class literal with all types in the Java language, including primitive types and type void
, to access the Class
object. This is useful, because the Class
object implements the getName()
method, which returns a fully-qualified name of the class. For example, when creating the MBeanAttributeInfo
object for the array attribute PhoneNumbers
in the User MBean, you could write the code as follows:
...
MBeanAttributeInfo numbers = new MBeanAttributeInfo(
"PhoneNumbers",
String[].class.getName(), // fully qualified class name
"User's phone numbers.",
true, true, false
);
...
So, instead of using the cumbersome textual representation of "[Ljava.lang.String;"
for an array of strings, you can let the Java class libraries do the work for you by accessing the array class and then calling its getName()
method. The same approach works for a multidimensional array of primitive types, for example, long[][][]
or even for type void
. You can test it with the following snippet of Java code:
System.out.println(void.class.getName()); System.out.println(long[][][].class.getName());
The MBeanParameterInfo
describes the parameters for MBean constructors and operations. As with the other metadata classes, the MBeanParameterInfo
inherits the name and description properties from its superclass MBeanFeatureInfo
. The name
property, along with the description string, is often displayed to the user with generic management tools and should contain the required information for the user to be able to either create a new instance of the MBean or invoke one of its operations. In addition, the MBeanParameterInfo
class contains the type of the parameter, again a fully qualified classname of the type. This string is accessible through the getType()
method of the MBeanParameterInfo
class.
The MBeanOperationInfo
class describes the management operations of an MBean. The object contains information about the operation's name and the description, both inherited from the MBeanFeatureInfo
class, the type of the method's return value, the method's signature, and the impact of the operation on the MBean.
The signature consists of an array of MBeanParameterInfo
objects that distinguishes overloaded methods from one another. The return type, as usual, must be a fully qualified classname of the returned object instance. The impact
value can be used by the MBean developer to give the management application some idea how the invocation of the operation is going to affect the MBean's state.
The JMX API defines four values that can be used to describe the method's impact. The constants shown in Table 4.3 are declared in the MBeanOperationInfo
class.
Table 4.3. The Impact of the MBeanOperationInfo
Class
Impact | Description |
---|---|
INFO
| The management operation is read-only, not affecting the state of the MBean. |
ACTION
| The management operation does affect the state of the MBean. This usually means changing the values of the MBean object's fields, changing the persistent state of the MBean or changing its configuration. |
ACTION_INFO
| The management operation implements both read-only types of functionality and also changes the state of the MBean in some way. |
UNKNOWN
| The effect of the operation is unknown. This is reserved for the operations of the Standard MBeans. |
The impact
field can be used to implement a coarse-grained scheme of determining which operations should be exposed to the user in the client. It is the responsibility of the Dynamic MBean developer to assign the correct impact values in the MBeanOperationInfo
metadata objects. The class provides the accessor method getImpact()
for querying the impact of the operation.
Listing 4.5 shows the metadata implementation of one of the User MBean operations, addPhoneNumber()
, as it would be implemented with a Dynamic MBean. Notice that the signature of the management operation is built using an array of MBeanParameterInfo
objects, each of which will include the name of the parameter, its runtime type, and a description of its purpose. The impact value is set to MBeanOperationInfo.ACTION
as the addPhoneNumber()
changes the state of the DynamicUser
object.
Example 4.5. DynamicUser.java
... // addPhoneNumber operation metadata MBeanOperationInfo addPhoneNumber = new MBeanOperationInfo( "addPhoneNumber", // operation name "Add a new phone number.", // description // parameters new MBeanParameterInfo[] { new MBeanParameterInfo( "number", // parameter name String.class.getName(), // parameter type "The number to add." // parameter descr. ) } , void.class.getName(), // return type void MBeanOperationInfo.ACTION // impact: ACTION ); ...
The MBeanConstructorInfo
class describes the constructors of an MBean that are exposed to management. The MBeanConstructorInfo
contains a name for the constructor, a description string for the management applications to display to the user, and an array of metadata objects describing the parameters of the constructor. The parameters make up the signature of the constructor that is used to differentiate a set of constructors from each other. You build up the parameters by using the MBeanParameterInfo
objects and can query the array by calling the getSignature()
method of the MBeanConstructorInfo
class.
For example, to define a default, no-args constructor for the User MBean, create the metadata object in Listing 4.6.
Notifications are part of the MBean's management interface, so the MBeanNotificationInfo
class is required to describe the notifications broadcast by the MBean. The management applications can use the MBeanNotificationInfo
object to discover the details of the notifications emitted by the MBean.
The use of the name
field in the MBeanNotificationInfo
class differs slightly from the other metadata classes in the JMX API. In the case of the notifications, the meaning of the field is restricted to contain the fully qualified classname of the actual notification object that is being broadcast. The description
can be freely formed to describe the purpose of the notification to the management applications.
In addition to the name and description, the MBeanNotificationInfo
class describes all the notification types that are sent using the class declared in the name
field. The notification types are accessible to the management applications via the getNotifTypes()
method of the MBeanNotificationInfo
class.
You may remember from Chapter 3, “Standard MBeans,” where notifications were discussed, that the MBeanNotificationInfo
is used with both the Standard MBean and Dynamic MBean implementations.
In the BroadcastingUser
MBean, you created the following MBeanNotificationInfo
instances (see Listing 4.7).
Example 4.7. Notification Metadata
new MBeanNotificationInfo[] { // notification for the remove operation new MBeanNotificationInfo( new String[] { "example.user.remove" } , // notif. types Notification.class.getName(), // notif. class "User Notifications." // description ), // attribute change notification type new MBeanNotificationInfo( new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE } , AttributeChangeNotification.class.getName(), "User attribute change notification." ) } ;
This code snippet creates the metadata for two different notification class types, Notification
and AttributeChangeNotification
, each of which is used for sending one notification type.
The Attribute
class is a convenience class in the JMX API for representing the management attributes of an MBean. The class associates the attribute name with its value and provides the accessor methods for reading these values. It is used in the JMX interfaces, such as the DynamicMBean
interface in getAttribute()
and setAttribute(
) methods.
The AttributeList
class is a subclass of the ArrayList
class in the Java Collection framework. It has been extended to accept Attribute
object instances in its insertion methods to ensure type safety in the collection. The AttributeList
is used in the DynamicMBean
interface setAttributes()
and getAttributes()
methods.
Similar to Standard MBeans, Dynamic MBeans can inherit their management interface definition from the superclass. This follows from the normal rules of Java inheritance. If the resource class does not directly implement either the Standard MBean interface or the DynamicMBean
interface, the inheritance tree is searched for the first ancestor implementing either one of the interfaces. This interface is then used to expose the management attributes and operations of an MBean. Remember, the same class is not allowed to implement both the Standard MBean and the DynamicMBean
interfaces. Such resources are not compatible with the JMX specification.
The next example completes the code snippets you have seen in this chapter so far with a full Dynamic MBean implementation of the User resource. You will build an identical managed resource to the User MBean to demonstrate the differences between instrumenting a resource using either the Standard MBean mechanism or the Dynamic MBean mechanism.
You will find that the Dynamic MBean requires quite a bit of extra code to implement the same management interface as was used with the Standard MBean. This time, you cannot rely on the agent to discover the management interface of the MBean automatically via introspection. You must provide the management interface metadata to the agent explicitly via the getMBeanInfo()
method. With non-trivial management interfaces, this can become quite a burden to the programmer. Later, in Part II, “JMX in the J2EE Platform,” you will see how to alleviate the problem by introducing an MBean implementation that can read its management interface definition from an XML file.
In addition, what is different with the Dynamic MBean from the Standard MBean implementation is that you no longer have a statically typed Java interface that describes the management attributes and operations of an MBean. Instead, the developer is left with the responsibility of correctly mapping the management attribute and operation names to their corresponding fields and methods in the resource class. The developer must explicitly handle the type casts between the invocations and the actual runtime types. Again, this places the burden on the programmer to ensure the correct implementation.
However, for these same reasons, Dynamic MBeans do allow for increased flexibility in how they implement the management attributes and operations. You can define the management interface declaratively by implementing a Dynamic MBean that reads an XML file describing the management interface. You could also implement a Dynamic MBean that retrieves its management interface from a database. This is possible because the management interface of Dynamic MBeans can be determined at runtime instead of compile time, which is a requirement for Standard MBeans. In addition, with Dynamic MBeans you gain access to the MBean's metadata elements that you can extend for the application purposes. You will see more examples of this in Chapter 5.
The code example in Listing 4.8 shows you a full Dynamic MBean implementation, in detail, of a resource that has a matching management interface to the User MBean from Chapter 3. The implementation starts with the code of the getAttribute()
and getAttributes()
methods. After the getter method implementations is the corresponding implementation of the setAttribute()
and setAttributes()
methods. These methods map the management attribute names to the actual fields in the resource implementation.
After the management attribute getter and setter method implementations, you have an implementation of the invoke()
method. The principal is the same as the management attribute accessor methods; the management operation names are mapped to the actual methods in the resource implementation.
Last, you have the implementation of the getMBeanInfo()
method. The implementation returns a collection of metadata objects back to the agent that describe each element of the resource's management interface.
The DynamicUser
implementation is shown in Listing 4.8.
Example 4.8. DynamicUser.java
package book.jmx.examples; import javax.management.*; import java.util.Iterator; public class DynamicUser extends NotificationBroadcasterSupport implements DynamicMBean { // mgmt attribute name constants final static String ID = "ID"; final static String NAME = "Name"; final static String ADDRESS = "Address"; final static String NUMBERS = "PhoneNumbers"; final static String PASSWD = "Password"; // mgmt operation name constants final static String PRINT_INFO = "printInfo"; final static String ADD_NUMBER = "addPhoneNumber"; final static String REMOVE_NUMBER = "removePhoneNumber"; // fields for attribute values private long id = System.currentTimeMillis(); private String name = ""; private String address = ""; private String passwd = null; private String[] numbers = new String[3]; // getAttribute implementation public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { if (attribute == null || attribute.equals("")) throw new IllegalArgumentException( "null or empty attribute name" ); // map the named attributes to fields if (attribute.equals(NAME)) return name; if (attribute.equals(ADDRESS)) return address; if (attribute.equals(NUMBERS)) return numbers; if (attribute.equals(ID)) return new Long(id); throw new AttributeNotFoundException( "Attribute " + attribute + " not found." ); } // getAttributes implementation public AttributeList getAttributes(String[] attributes) { if (attributes == null) throw new IllegalArgumentException("null array"); AttributeList list = new AttributeList(); for (int i = 0; i < attributes.length; ++i) { try { list.add(new Attribute( attributes[i], getAttribute(attributes[i]) )); } catch (JMException ignored) { // if the attribute could not be retrieved, skip it } } return list; } // setAttribute implementation public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { if (attribute == null) throw new AttributeNotFoundException("null attribute"); // map attributes to fields try { if (attribute.getName().equals(NAME)) this.name = (String)attribute.getValue(); else if (attribute.getName().equals(ADDRESS)) this.address = (String)attribute.getValue(); else if (attribute.getName().equals(NUMBERS)) this.numbers = (String[])attribute.getValue(); else if (attribute.getName().equals(PASSWD)) this.passwd = (String)attribute.getValue(); else throw new AttributeNotFoundException( "attribute "+attribute.getName()+" not found." ); } catch (ClassCastException e) { throw new InvalidAttributeValueException( "Invalid attribute type " + attribute.getValue().getClass().getName() ); } } // setAttributes implementation public AttributeList setAttributes(AttributeList list) { if (list == null) throw new IllegalArgumentException("null list"); AttributeList results = new AttributeList(); Iterator it = list.iterator(); while (it.hasNext()) { try { Attribute attr = (Attribute)it.next(); setAttribute(attr); results.add(attr); } catch (JMException ignored) { // if unable to set the attribute, skip it } } return results; } // invoke implementation public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { if (actionName == null || actionName.equals("")) throw new IllegalArgumentException("no operation"); // map operation names to methods if (actionName.equals(PRINT_INFO)) return printInfo(); else if (actionName.equals(ADD_NUMBER)) { addPhoneNumber((String)params[0]); return null; } else if (actionName.equals(REMOVE_NUMBER)) { removePhoneNumber(((Integer)params[0]).intValue()); return null; } else throw new UnsupportedOperationException ( "unknown operation " + actionName ); } // getMBeanInfo implementation public MBeanInfo getMBeanInfo() { // Is attribute readable and/or writable final boolean READABLE = true; final boolean WRITABLE = true; // Is attribute getter in boolean isAttribute() // form? final boolean IS_GETTER = true; // MBean class and description String className = getClass().getName(); String description = "User resource with dynamic management interface"; // metadata for 'ID' attribute. MBeanAttributeInfo id = new MBeanAttributeInfo( ID, // name long.class.getName(), // type "Unique identifier of user.", // description READABLE, !WRITABLE, !IS_GETTER // access ); // metadata for 'Name' attribute. MBeanAttributeInfo name = new MBeanAttributeInfo( NAME, // name String.class.getName(), // type "User's name.", // description READABLE, WRITABLE, !IS_GETTER // access ); // metadata for 'Address' attribute. MBeanAttributeInfo address = new MBeanAttributeInfo( ADDRESS, // name String.class.getName(), // type "User's address.", // description READABLE, WRITABLE, !IS_GETTER // access ); // metadata for 'PhoneNumbers' attribute. MBeanAttributeInfo numbers = new MBeanAttributeInfo( NUMBERS, // name String[].class.getName(), // type "User's phone numbers.", // description READABLE, WRITABLE, !IS_GETTER // access ); // metadata for 'Password' attribute. MBeanAttributeInfo passwd = new MBeanAttributeInfo( PASSWD, // name String.class.getName(), // type "User's password.", // description !READABLE, WRITABLE, !IS_GETTER // access ); // metadata for 'printInfo' operation MBeanOperationInfo printInfo = new MBeanOperationInfo( PRINT_INFO, // name "String representation of the user.", // description null, // signature String.class.getName(), // return type MBeanOperationInfo.INFO // impact ); // metadata for 'addPhoneNumber' operation. MBeanOperationInfo addPhoneNumber = new MBeanOperationInfo( ADD_NUMBER, // name "Adds phone number for the user.", // description new MBeanParameterInfo[] { // signature new MBeanParameterInfo( "number", String.class.getName(), "The number to add." ) } , void.class.getName(), // return type MBeanOperationInfo.ACTION // impact ); // metadata for 'removePhoneNumber' operation. MBeanOperationInfo removePhoneNumber = new MBeanOperationInfo( REMOVE_NUMBER, // name "Removes phone number from user.", // description new MBeanParameterInfo[] { // signature new MBeanParameterInfo( "index", int.class.getName(), "The index number." ) } , void.class.getName(), // return type MBeanOperationInfo.ACTION // impact ); // mbean constructors MBeanConstructorInfo defaultConstructor = new MBeanConstructorInfo( "Default Constructor", "Creates a new user instance.", null ); // attribute, constructor and operation lists MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[] { id, name, address, numbers, passwd } ; MBeanConstructorInfo[] constructors = new MBeanConstructorInfo[] { defaultConstructor } ; MBeanOperationInfo[] operations = new MBeanOperationInfo[] { printInfo, addPhoneNumber, removePhoneNumber } ; // return the MBeanInfo return new MBeanInfo( className, description, attributes, constructors, operations, null ); } // management operation implementations public String printInfo() { return "User: " + name +" n"+ "Address: " + address +" n"+ "Phone #: " + numbers[0] +" n"+ "Phone #: " + numbers[1] +" n"+ "Phone #: " + numbers[2] +" n"; } public void addPhoneNumber(String number) { for (int i = 0; i < numbers.length; ++i) if (numbers[i] == null) { numbers[i] = number; break; } } public void removePhoneNumber(int index) { if (index < 0 || index >= numbers.length) return; numbers[index] = null; } }
Because the management operations and attributes of the DynamicUser
MBean are identical to the UserMBean
, you can use the same management application to access both managed resources. You can test this by taking the UserClient
class you built in Chapter 3 and run it against an MBean server instance that has a DynamicUser
instance registered to it. The DynamicUser
instance should be bound to the same object name
reference "example:name=user"
that was used for the UserMBean
.
You will need to modify the UserClient
class to register DynamicUser
class instead of the User. The change to UserClient
class is shown in the Listing 4.9.
Example 4.9. UserClient.java
... try { // register the MBean ObjectName name = new ObjectName("example:name=user"); server.registerMBean(new DynamicUser(), name); // Invoke the printInfo operation on an // uninitialized MBean instance. Object result = server.invoke( name, // MBean name "printInfo", // operation name null, // no parameters null // void signature ); ...
When you compile and run the modified UserClient
that uses the DynamicUser
component, you should see the exact same results on the console that you saw with the UserMBean
.
To compile the DynamicUser.java
and the modified UserClient.java
execute the command:
C: Examples> javac -d . -classpath jmx-1_0_1-ri_bin jmx lib jmxri.jar; jmx-1_0_1-ri_bin jmx lib jmxtools.jar DynamicUser.java UserClient.java
To run the application, enter the command:
C: Examples> java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar book.jmx.examples .UserClient
You should see the following output:
Non-initialized User object:
User: Address: Phone #: null Phone #: null Phone #: null Initialized User object: User: John Address: Strawberry Street Phone #: 555-1232 Phone #: null Phone #: null
The difference in the agent between the standard and Dynamic MBean is how the management interface metadata is retrieved. For the Standard MBeans, it was collected by the agent using introspection, whereas for the Dynamic MBeans, the agent invokes the getMBeanInfo()
method to retrieve the metadata. The agent abstracts the MBean type, standard or dynamic, from the management application by requiring all invocations to propagate through its invoke()
, setAttribute()
, and getAttribute()
methods. What is exposed to the management application is the MBeanServer
interface that provides the methods to access and discover the management interface.
The metadata description available to the client is either built by the agent or provided by the managed resource explicitly. This, added to the fact that the client is never given a direct Java reference to the managed resource, but instead uses the object name reference, leads to a decoupled invocation mechanism. There is no MBean specific, statically typed interface that the management application needs to rely on to access the managed resource. The next section will give you a simplified example on how to switch component implementations between Standard and Dynamic MBean on a live MBean server.
You have built two separate implementations of a resource that have an identical management interface. This means that, from the point of view of the management application, these implementations are interchangeable. You have also learned that the JMX architecture decouples the management applications from the managed resources, and seen how the operation invocations are detyped in the MBean server invoke()
method.
These features of the JMX-based management architecture allow you to implement a managed resource that you can change or update at runtime. This is often called hot deployment, a feature you can find in many server-based software applications. It means that a new component can be brought into the server or an existing one can be updated without having to restart the whole server. With JMX this is easy to do. Because the management applications do not have any direct Java references to the managed resources you can switch the component implementations without the client necessarily noticing anything.
To demonstrate this, you will build a Recycler
application that will allow you to switch the implementation of an MBean on-the-fly. You will use the DynamicUser
MBean built in Listings 4.8 and the BroadcastingUser
MBean that you built in Chapter 3. The Recycler
component will bind one of the two implementations to the MBean server under the object name "example:name=user"
.
The Recycler
component itself is an MBean, and exposes a recycle()
management operation to the client. You can use the recycle()
operation to switch the implementation between a Standard MBean and Dynamic MBean version. The management interface of the RecyclerMBean
is as follows:
package book.jmx.examples; import javax.management.*; public interface RecyclerMBean { public String recycle(); }
The recycle()
operation will switch the implementation bound to object name "example:name=user"
in the MBean server. The RecyclerMBean
will also register and start an HTTP adaptor, so that you can manipulate the components easily via your Web browser. The code for the Recycler
implementation is shown in Listing 4.10.
Example 4.10. Recycler.java
package book.jmx.examples; import javax.management.*; import java.util.List; public class Recycler implements RecyclerMBean { private String component = "Standard User"; private MBeanServer server = null; public String recycle() { // Find an agent from this JVM. Null argument will // return a list of all MBeanServer instances. List list = MBeanServerFactory.findMBeanServer(null); server = (MBeanServer)list.iterator().next(); try { // switch implementations between a standard and // dynamic mbeans if (component.equals("Standard User")) { server.unregisterMBean( new ObjectName("example:name=User") ); server.registerMBean( new DynamicUser(), new ObjectName("example:name=User") ); component = "Dynamic User"; } else { server.unregisterMBean( new ObjectName("example:name=User") ); server.registerMBean( new BroadcastingUser(), new ObjectName("example:name=User") ); component = "Standard User"; } } catch (JMException e) { e.printStackTrace(); } return "Implementation changed to " + component; } public static void main(String[] args) { MBeanServer server = MBeanServerFactory.createMBeanServer(); ObjectName name = null; try { name = new ObjectName("example:name=recycler"); server.registerMBean(new Recycler(), name); name = new ObjectName("example:name=User"); server.registerMBean(new BroadcastingUser(), name); name = new ObjectName("adaptor:protocol=HTTP"); com.tivoli.jmx.http_pa.Listener adaptor = new com.tivoli.jmx.http_pa.Listener(); adaptor.startListener(); server.registerMBean(adaptor, name); } catch (java.io.IOException e) { e.printStackTrace(); } catch (JMException e) { e.printStackTrace(); } } }
To compile and run the Recycler example, execute these commands (using Tivoli JMX):
C: Examples> javac -d . -classpath .;tmx4j base lib jmxx.jar;tmx4j base lib jmxc .jar;tmx4j base lib log.jar; tmx4j ext lib jmxext.jar Recycler.java RecyclerMBean.java C: Examples> java -classpath .;tmx4j base lib jmxx.jar; tmx4j base lib jmxc.jar; tmx4j base lib log.jar;tmx4j ext lib jmxext.jar book.jmx.examples.Recycler
When you successfully run the application, the following line will be printed on the console:
The HTTP Adaptor started on port number 6969
Point your browser to http://localhost:6969 and enter *:*
in the Find an MBean query page to get a list of registered MBeans. Select the Recycler MBean by clicking link “example:name=recycler”. Execute the recycle()
operation to switch the User implementation between the Dynamic MBean and Standard MBean.
Notice that the BroadcastingUser
exposes management notifications as part of its management interface. You can see the difference on the HTTP adaptor Web pages in Figures 4.2,4.3 and 4.4 where the notifications are listed. Another difference worth noticing is that the DynamicUser
MBean shows the descriptions for its management operations. These were added as part of the MBeanOperationInfo
metadata classes. The ability to access and extend the metadata attached to each element of the management interface is an interesting possibility that you will utilize in the later chapters.
There are some features that are lacking in the Recycler
example. For example, the change of the implementation will not store the state of the MBean. If you have edited the attribute values, they will be lost. To avoid this, you need to persist the MBean's state to the disk, to a database, or some other persistent storage. In Chapter 5, you will look at the Model MBeans and how they implement the MBean persistence.
The other restriction with the Recycler
example is that both components need to be available for the JVM classloader at start-up time. In practice, when you need to hot deploy components, whether you are updating an existing one or adding new ones, you will need to dynamically add the new classes to the class loader. The JMX provides this possibility with a standard agent service called M-Let service. This allows you to add new components dynamically and load them from across the network. You will see the M-Let service in Chapter 7, “Standard Agent Services,” where all the standard agent services are covered.
As you have seen with the examples, Dynamic MBeans allow the manageable resources to define their management interfaces at runtime, instead of using the statically typed Java interfaces that you needed to implement with Standard MBeans. The agent does not rely on introspection to find the management interface but, instead, directly calls the getMBeanInfo()
method of the DynamicMBean
interface to discover the management interface and to invoke operations and to get and set attributes. The Dynamic MBean implementation is required to build metadata that explicitly declares all the elements of the management interface.
Because the Dynamic MBeans do not impose a set of naming rules on the resource classes, they are convenient in exposing already existing resources to management. The attribute and operation invocations can easily be mapped to methods using any kind of naming convention.
An important point to remember about Dynamic MBeans is that they allow the late binding of the management interface instead of the early static binding of a Java interface. Another aspect differentiating the Dynamic MBeans from the Standard MBeans is the fact that the developer has an access to the metadata of the MBean. This is not possible with Standard MBeans.
3.148.104.242