The MBeanServer Interface

In this section, we will take a close look at the MBeanServer interface, which is used to communicate with the MBeanServer implementation. First we will present the interface in its entirety, then we will proceed to dissect the interface method by method, providing examples along the way. We will also take another look at the ObjectName class, which is critical in manipulating MBeans indirectly through the MBeanServer interface (the preferred means of doing so). We have already covered what you can do through a reference to the MBeanServer interface. Let’s now look more closely at this important interface. Example 6-2 shows the MBeanServer interface.

Example 6-2. The MBeanServer interface

package javax.management;
  
public interface MBeanServer { 
  
  public Object instantiate(String className) 
    throws ReflectionException, MBeanException;
  
  public Object instantiate(String className, ObjectName loaderName) 
    throws ReflectionException, MBeanException, InstanceNotFoundException;
  
  public Object instantiate(String className, Object params[], String signature[])
    throws ReflectionException, MBeanException; 
  
  public Object instantiate(String className, ObjectName loaderName,
                            Object params[], String signature[]) 
    throws ReflectionException, MBeanException, InstanceNotFoundException;
  
  public ObjectInstance registerMBean(Object object, ObjectName name)
    throws InstanceAlreadyExistsException, MBeanRegistrationException,
           NotCompliantMBeanException;
  
  public ObjectInstance createMBean(String className, ObjectName name)
    throws ReflectionException, InstanceAlreadyExistsException,
           MBeanRegistrationException, MBeanException, NotCompliantMBeanException;
  
  public ObjectInstance createMBean(String className, ObjectName name,
                                    ObjectName loaderName) 
    throws ReflectionException, InstanceAlreadyExistsException,
           MBeanRegistrationException, MBeanException,
           NotCompliantMBeanException, InstanceNotFoundException;
  
  public ObjectInstance createMBean(String className, ObjectName name,
                                    Object params[], String signature[]) 
    throws ReflectionException, InstanceAlreadyExistsException,
           MBeanRegistrationException, MBeanException, NotCompliantMBeanException;
  
  public ObjectInstance createMBean(String className, ObjectName name,
                                    ObjectName loaderName, Object params[],
                                    String signature[]) 
    throws ReflectionException, InstanceAlreadyExistsException,
           MBeanRegistrationException, MBeanException,
           NotCompliantMBeanException, InstanceNotFoundException;
  
  public void unregisterMBean(ObjectName name)
    throws InstanceNotFoundException, MBeanRegistrationException; 
  
  public Object getAttribute(ObjectName name, String attribute)
    throws MBeanException, AttributeNotFoundException,
           InstanceNotFoundException, ReflectionException;
  
  public AttributeList getAttributes(ObjectName name, String[] attributes)
    throws InstanceNotFoundException, ReflectionException;
  
  public void setAttribute(ObjectName name, Attribute attribute)
    throws InstanceNotFoundException, AttributeNotFoundException,
           InvalidAttributeValueException, MBeanException, ReflectionException;
  
  public AttributeList setAttributes(ObjectName name, AttributeList attributes)
    throws InstanceNotFoundException, ReflectionException;
  
  public Object invoke(ObjectName name, String operationName,
                       Object params[], String signature[])
    throws InstanceNotFoundException, MBeanException, ReflectionException;
  
  public MBeanInfo getMBeanInfo(ObjectName name)
    throws InstanceNotFoundException, IntrospectionException, ReflectionException;
  
  public void addNotificationListener(ObjectName name, NotificationListener listener,
                                      NotificationFilter filter, Object handback)
    throws InstanceNotFoundException;
  
  public void addNotificationListener(ObjectName name, ObjectName listener, 
                                      NotificationFilter filter, Object handback)
    throws InstanceNotFoundException;
  
  public void removeNotificationListener(ObjectName name, NotificationListener listener)
    throws InstanceNotFoundException, ListenerNotFoundException;
  
  public void removeNotificationListener(ObjectName name, ObjectName listener) 
    throws InstanceNotFoundException, ListenerNotFoundException;
  
  public Set queryMBeans(ObjectName name, QueryExp query);
  
  public Set queryNames(ObjectName name, QueryExp query);
  
  public ObjectInstance getObjectInstance(ObjectName name)
    throws InstanceNotFoundException;
  
  public boolean isRegistered(ObjectName name);
  
  public Integer getMBeanCount(  );
  
  public boolean isInstanceOf(ObjectName name, String className)
    throws InstanceNotFoundException;
  
  public String getDefaultDomain(  );
  
  public ObjectInputStream deserialize(ObjectName name, byte[] data)
    throws InstanceNotFoundException, OperationsException;
  
  public ObjectInputStream deserialize(String className, byte[] data)
    throws OperationsException, ReflectionException;
  
  public ObjectInputStream deserialize(String className, ObjectName loaderName,
                                       byte[] data)
    throws InstanceNotFoundException, OperationsException, ReflectionException;
}

As you can see from Example 6-2, the MBeanServer interface contains quite a few methods! These methods can be grouped into five distinct categories, related to the function each method performs:

Instantiation and registration

Those methods related to instantiation of MBeans, registration of MBeans, or both

Indirect MBean manipulation

Those methods related to manipulating MBeans through the MBean server, rather than through direct references to the MBeans themselves

Notification

Those methods related to MBean notifications

Query

Those methods related to retrieving subsets of registered MBeans by querying the MBean server

Utility

Those methods that provide helpful functionality not directly related to any particular previous category

In this section, we will look at each method on the MBeanServer interface by category. We will start with instantiation and registration, as that category of methods is likely to be the most widely used. The other categories will follow, in the order in which they are enumerated above.

Instantiation and Registration

If you are interested only in instrumenting your application resources as MBeans, you will be concerned with only those methods of MBeanServer that allow you to instantiate and register MBeans. Two of these methods, createMBean( ) and instantiate( ), are overloaded, with four overloads apiece. Two other methods, registerMBean( ) and unregisterMBean( ), round out the instantiation and registration methods. These methods can be broken down into four categories:

  • Instantiating an MBean

  • Registering an instantiated MBean

  • Combining the instantiation and registration of an MBean

  • Removing a registered MBean from the MBean server

Instantiating an MBean

The four overloads of instantiate( ) are defined as:

public Object instantiate(String className)
  throws ReflectionException, MBeanException;
  
public Object instantiate(String className, Object params[], String signature[]) 
  throws ReflectionException, MBeanException;
  
public Object instantiate(String className, ObjectName loaderName)
  throws ReflectionException, MBeanException, InstanceNotFoundException;
  
public Object instantiate(String className, ObjectName loaderName,
                          Object params[], String signature[])
  throws ReflectionException, MBeanException, InstanceNotFoundException;

Each of these methods is used to create a new instance of an MBean’s class, as the method name suggests. Upon creation, a reference to the newly created MBean is returned to the caller in the form of an Object reference. The caller is then responsible for registering the MBean with the MBean server.

In all cases, the String className parameter is the fully qualified class name of the MBean’s class. For example, if the MBean class Queue to be loaded is found in the sample.mbeanserver package, the MBean would be instantiated as:

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String qmbeanClassName = "sample.mbeanserver.Queue";
try {
  Object qmbean = mbeanServer.instantiate(qmbeanClassName);
} catch (ReflectionException e) {
  // . . .
} catch (MBeanException e) {
// . . .
}

In this case, the no-argument constructor of Queue will be called when the object is constructed. Likewise, we can use the second version of instantiate( ) to have an alternate constructor invoked when an instance of Queue is created. The alternate constructor for Queue is an int that allows you to specify the depth of the queue. The signature of the instantiate( ) method we invoke is:

public Object instantiate(String className, Object params[], String signature[]) 
  throws ReflectionException, MBeanException;

The first parameter is the fully qualified name of the Queue class, as mentioned earlier. The second parameter is an array of Object instances that contain the actual parameter values. Any primitive types must be wrapped in the appropriate JDK wrapper class. For our example, we must wrap the int parameter with a java.lang.Integer instance. For object types, simply pass an instance of the object that contains the parameter value. The third parameter to instantiate( ) is a String array that contains the fully qualified class names of the constructor’s signature.

The alternate constructor for Queue is defined as:

public Queue(int queueSize) {
  // . . .
}

Suppose we want to use instantiate( ) to invoke this constructor when an instance of Queue is instantiated and set the queueSize parameter to 5:

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
Object[] params = new Object[] {
  new Integer(5);
};
String[] signature = new String[] {
  Integer.TYPE.getName(  )
};
String queueClassName = "sample.mbeanserver.Queue";
try {
  Object queue = mbeanServer.instantiate(queueClassName, params, signature);
  // . . .
} catch (ReflectionException e) {
  // . . .
} catch (MBeanException e) {
  // . . .
}

First we construct an array of Object instances (in this case, the array will contain only one instance), wrapping the primitive int with an instance of the JDK wrapper class Integer. Then we create an array of Strings (again, only one) containing the string representation of the Class object that corresponds to the parameter passed in the Object array. Because the parameter type is an int, we must obtain the string representation of an int. Integer.TYPE is the Class object for the primitive type int, and a call to getName( ) gives us the string representation we need. Chapter 3 contains a thorough discussion of the use of TYPE for obtaining the Class object for primitive types.

If Queue’s class loader can locate and successfully load Queue, an Object reference to the newly created Queue class is returned. If the class loader cannot locate Queue, the MBeanServer implementation will use its list of class loaders to load the class. However, there are two overloaded versions of instantiate( ) that have the same signature as the two versions we just discussed, with the exception of an additional parameter that allows us to specify an ObjectName of the class loader to use when instantiating the MBean.

There is a catch, though: the class loader to be used must be an MBean and must be registered with the MBean server prior to invoking these two versions of instantiate( ). It is beyond the scope of this book to show how to write a ClassLoader, and other than the additional parameter passed to them, the last two overloads of instantiate( ) work exactly the same way as their previous two counterparts. Be aware, though, that the ClassLoader you provide with these two overloads must be an MBean as well as an extended version of ClassLoader.

In each of the overloaded versions of instantiate( ), it is possible for something to go wrong. Notice the exceptions that are potentially thrown from each method. The most common exception you’re likely to see is a ReflectionException, which indicates that the intended constructor could not be found. For the first and third versions of instantiate( ), it means that the no-argument constructor either was not defined on the class (but an alternate constructor was) or does not have public visibility. For the second and fourth versions, a ReflectionException means that the constructor with the specified signature either does not exist on the class or does not have public visibility.

Registering an MBean

Once instantiate( ) has been called, an Object reference to the newly created MBean is returned to the caller. It is then up to the caller to register the MBean with the MBean server (although the caller is under no obligation to do so). MBeanServer provides a method that allows you to register the MBean once it has been instantiated. This method, called registerMBean( ), is defined as:

public ObjectInstance registerMBean(Object object, ObjectName name)
  throws InstanceAlreadyExistsException, MBeanRegistrationException,
         NotCompliantMBeanException;

The first parameter, object, is an Object reference to an instance of the MBean to be registered. The second parameter, name, is an ObjectName instance that contains the unique object name of the MBean. An MBean’s object name is a String that contains the domain and the key property list and is of the form:

"domain:property1=value1,property2=value2,. . .,propertyN=valueN"

Think of the domain as the namespace mechanism for JMX. The key property list for an MBean is a comma-separated list of name/value pairs that uniquely identify an MBean within a particular domain. Refer to Chapter 2 for a more thorough discussion of the ObjectName class and its role in MBean registration.

Note

From this point on, an MBean’s object name is the unique string identifying the MBean, and ObjectName is a class, an instance of which is used to contain the object name. For example:

ObjectName name1 = new ObjectName("d1:p1=v1");
ObjectName name2 = new ObjectName("d1:p1=v1");

name1 and name2 are unique ObjectNames, but they contain the same object name.

If the object name is not unique, the MBean server throws an InstanceAlreadyExistsException. If the MBean is not compliant with the JMX design patterns, a NotCompliantMBeanException is thrown. If any other problems crop up during the registration process, an MBeanRegistrationException is thrown. Using the Queue example from earlier in this section, we could create an instance of Queue using instantiate( ), then take the returned Object reference and call registerMBean( ):

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String qmbeanClassName = "sample.mbeanserver.Queue";
Object qmbean = null;
try {
  Object qmbean = mbeanServer.instantiate(qmbeanClassName);
} catch (ReflectionException e) {
  // . . .
} catch (MBeanException e) {
  // . . .
}
// Create an ObjectName for the MBean. . .
String domain = mbeanServer.getDefaultDomain(  );
String keyPropsList = "name=Queue";
ObjectName objName = new ObjectName(domainName + ":" + keyPropsList);
try {
  mbeanServer.registerMBean(qmbean, objName);
} catch (InstanceAlreadyExistsException e) {
  // . . .
} catch (NotCompliantMBeanException e) {
  // . . .
} catch (MBeanRegistrationException e) {
  // . . .
}

What is returned from the call to registerMBean( ) is an ObjectInstance, which encapsulates an MBean’s class name and its ObjectName instance (although the resulting ObjectName may actually be different if the MBean implements the MBeanRegistration interface and provides a different ObjectName in the preRegistration( ) method). In the above code snippet, we simply ignored the return value, as we didn’t need it for anything.

Instead of using instantiate( ) to register the Queue MBean, we could simply have used the new keyword to create an instance and then called registerMBean( ):

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String qmbeanClassName = "sample.mbeanserver.Queue";
QueueMBean qmbean = new Queue(  );
// Create an ObjectName for the MBean. . .
String domain = mbeanServer.getDefaultDomain(  );
String keyPropsList = "name=Queue";
ObjectName objName = new ObjectName(domainName + ":" + keyPropsList);
try {
  mbeanServer.registerMBean(qmbean, objName);
} catch (InstanceAlreadyExistsException e) {
  // . . .
} catch (NotCompliantMBeanException e) {
  // . . .
} catch (MBeanRegistrationException e) {
  // . . .
}

This is a perfectly acceptable approach to creating and registering an MBean, and one that I often use myself. There is no requirement that you instantiate your MBeans by calling instantiate( ); this method is simply provided as a convenience.

Combining the instantiation and registration of an MBean

The MBeanServer interface provides us with a method that can combine the instantiation and registration of an MBean. This method is called createMBean( ). createMBean( ) works in exactly the same way as instantiate( ), but it allows you to specify an ObjectName so that the details of registering your MBean are handled behind the scenes. Just as with instantiate( ), there are four overloads of createMBean( ), defined as:

public ObjectInstance createMBean(String className, ObjectName name)
  throws ReflectionException, InstanceAlreadyExistsException, 
         MBeanRegistrationException, MBeanException, NotCompliantMBeanException;
  
public ObjectInstance createMBean(String className, ObjectName name,
                                  ObjectName loaderName)
  throws ReflectionException, InstanceAlreadyExistsException, 
         MBeanRegistrationException, MBeanException,
         NotCompliantMBeanException, InstanceNotFoundException;
  
public ObjectInstance createMBean(String className, ObjectName name,
                                  Object params[], String signature[])
  throws ReflectionException, InstanceAlreadyExistsException,
         MBeanRegistrationException, MBeanException, NotCompliantMBeanException;
  
public ObjectInstance createMBean(String className, ObjectName name,
                                  ObjectName loaderName, Object params[],
                                  String signature[])
  throws ReflectionException, InstanceAlreadyExistsException,
         MBeanRegistrationException, MBeanException,
         NotCompliantMBeanException, InstanceNotFoundException;

As you can see from the method signatures of the overloaded versions of createMBean( ), the possible exceptions that may be thrown are a combination of those of the corresponding instantiate( ) and registerMBean( ) methods. Using the Queue MBean example from earlier, we can combine the instantiation and registration processes with one call to createMBean( ):

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String qmbeanClassName = "sample.mbeanserver.Queue";
String domain = mbeanServer.getDefaultDomain(  );
String keyPropsList = "name=Queue";
ObjectName objName = new ObjectName(domainName + ":" + keyPropsList);
ObjectInstance objInst = null;
try {
  objInst = mbeanServer.createMBean(qmbeanClassName, objName);
} catch (ReflectionException e) {
  // . . .
} catch (InstanceAlreadyExistsException e) {
  // . . .
} catch (MBeanRegistrationException e) {
  // . . .
} catch (MBeanException e) {
  // . . .
} catch (NotCompliantMBeanException e) {
  // . . .
}

There is a subtle—yet important—difference between using createMBean( ) and the other approaches we’ve discussed for creating and registering MBeans: when you use createMBean( ) to instantiate and register an MBean, you will not receive a reference to the MBean object itself. This means that you will not be able to directly manipulate the MBean, as you do not have a reference to it. Notice what is returned from createMBean( ): a reference to the MBean’s ObjectInstance (as we’ve already mentioned, every registered MBean has a corresponding ObjectInstance associated with it). This is an indirect reference to the MBean that you can use to manipulate the MBean indirectly. In the next section, we will look at how to indirectly manipulate MBeans using the MBean server and both an ObjectInstance and an ObjectName reference to the MBean.

Removing a registered MBean from the MBean server

The MBeanServer interface also provides a means to remove MBeans from the MBean server’s registry. This has no effect on the object itself—if there are any valid references to it, it remains alive and well inside the JVM. However, once removed from the MBean server’s internal registry, the MBean is no longer accessible through the MBean server to other MBeans, JMX agents, or management applications. The method to remove an MBean from the MBean server is called unregisterMBean( ) and is defined as:

public void unregisterMBean(ObjectName name)
  throws InstanceNotFoundException, MBeanRegistrationException;

All that is required to unregister an MBean is its object name, wrapped in an ObjectName instance. If the object name string contained within the ObjectName is not found in the MBean server’s registry, an InstanceNotFoundException is thrown. If any other error occurs, the MBean server throws an MBeanRegistrationException.

Here is an example, using the Queue MBean from earlier:

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String qmbeanClassName = "sample.mbeanserver.Queue";
String domain = mbeanServer.getDefaultDomain(  );
String keyPropsList = "name=Queue";
ObjectName objName = new ObjectName(domainName + ":" + keyPropsList);
// register the MBean. . .
// later on . . .
try {
  mbeanServer.unregisterMBean(objName);
} catch (InstanceNotFoundException e) {
  // . . .
} catch (MBeanRegistrationException e) {
  // . . .
}

If the call to unregister( ) is successful, the MBean specified by the object name string contained within the ObjectName reference passed to unregister( ) is removed from the MBean server’s registry and is no longer accessible to other MBeans, JMX agents, or management applications. However, as mentioned earlier, any direct references to the MBean object will still be valid. Calling unregister( ) does not guarantee that an MBean will be eligible for garbage collection.

Indirect MBean Manipulation

The MBean server provides several methods that allow for interaction with a registered MBean through its object name. Recall that when an MBean is registered, a unique object name—a string containing the MBean’s domain and key property list—is always provided by passing an instance of ObjectName that contains the object name. Subsequently, through an ObjectName instance that contains the object name of the MBean, any MBean can be manipulated through these methods on the MBean server. The MBean server looks up the MBean by its object name and, if it finds it, manipulates the MBean directly on behalf of the caller, receives the results, and returns the results to the caller. This is what is meant by indirect MBean manipulation—because the caller does not have a reference to the MBean object itself, it uses the MBean’s unique object name to manipulate the MBean and uses the MBean server to broker the interaction.

By “manipulating” or “interacting with” an MBean, we mean using the management interface of an MBean to:

  • Retrieve a management attribute value.

  • Set a management attribute value.

  • Invoke a management operation.

Through indirect MBean manipulation, only the management interface is available. For example, suppose I have a management interface defined on MyClassMBean:

public interface MyClassMBean {
  public String getStringAttribute(  );
  public void reset(  );
}

that’s implemented on a class MyClass (following the standard MBean design patterns):

public class MyClass {
  // management interface. . .
  public String getStringAttribute(  ) { 
    return _stringAttribute; 
  }
  public void reset(  ) {
    setStringAttribute("");
  }
  // other class-related stuff. . .
  private String _stringAttribute;
  public void setStringAttribute(String value) {
    _stringAttribute = value;
  }
}

Notice the public setter setStringAttribute( ). If we create an instance of MyClass, we are free to invoke this method because we have a reference to it:

MyClass myClass = new MyClass(  );
myClass.setAttribute("I am Roger the Shrubber.");

However, this method is not available through the management interface of MyClassMBean. The following code will not compile:

MyClassMBean myClass = new MyClass(  );
myClass.setAttribute("I'm a lumberjack and I'm okay.");

It is the same with indirect MBean manipulation. Only the management interface of the MBean can be manipulated. The MBean server provides the following methods for indirectly manipulating MBeans:

public Object getAttribute(ObjectName name, String attribute)
  throws MBeanException, AttributeNotFoundException,
         InstanceNotFoundException, ReflectionException;
  
public AttributeList getAttributes(ObjectName name, String[] attributes)
  throws InstanceNotFoundException, ReflectionException;
  
public void setAttribute(ObjectName name, Attribute attribute)
  throws InstanceNotFoundException, AttributeNotFoundException,
         InvalidAttributeValueException, MBeanException, ReflectionException;
  
public AttributeList setAttributes(ObjectName name, AttributeList attributes)
  throws InstanceNotFoundException, ReflectionException;
  
public Object invoke(ObjectName name, String operationName,
                     Object params[], String signature[])
  throws InstanceNotFoundException, MBeanException, ReflectionException;
  
public MBeanInfo getMBeanInfo(ObjectName name)
  throws InstanceNotFoundException, IntrospectionException, ReflectionException;

If these methods look familiar, it is because they have the same names as (and perform the same functions as) the corresponding methods on the DynamicMBean interface. In fact, even the parameters are the same, with the exception that the first parameter to each method above is an ObjectName to identify the MBean with which to interact. See Chapter 3 if you are not familiar with what these methods do and for a thorough discussion of the Attribute, AttributeList, and MBeanInfo classes.

The only difference between the signatures of these methods and their DynamicMBean counterparts is the possible exceptions that can be thrown and, as mentioned earlier, the addition of a parameter allowing you to specify the MBean’s object name. For example, the getAttribute( ) method of MBeanServer throws an additional exception called InstanceNotFoundException that the getAttribute( ) method of DynamicMBean does not, in the event that the specified object name is not registered in the MBean server.

Notification

Through the MBean server, you can register an interest in receiving notifications from any registered MBean that is a notification broadcaster. Notification broadcasters must implement the NotificationBroadcaster interface. Similarly, you can unregister interest in receiving these notifications through the MBean server.

There are four methods on the MBeanServer interface that deal with notifications. They are defined as:

public void addNotificationListener(ObjectName name, NotificationListener listener,
                                   NotificationFilter filter, Object handback)
  throws InstanceNotFoundException;
  
public void addNotificationListener(ObjectName name, ObjectName listener,
                                    NotificationFilter filter, Object handback)
  throws InstanceNotFoundException;
  
public void removeNotificationListener(ObjectName name, 
                                       NotificationListener listener)
  throws InstanceNotFoundException, ListenerNotFoundException;
  
public void removeNotificationListener(ObjectName name, ObjectName listener) 
  throws InstanceNotFoundException, ListenerNotFoundException;

To register interest in receiving a notification, use addNotificationListener( ) . To unregister interest in receiving a notification, use removeNotificationListener( ) . There are two versions of each of these methods. The difference between the respective versions is that you can specify either an Object reference to a notification listener (a class that implements the NotificationListener interface) or the object name of the notification listener (in which case the notification listener must also be a registered MBean). We will look more closely at notification broadcasters and listeners in the next chapter.

The name parameter is the ObjectName of the notification broadcaster. The listener parameter is either a reference to the notification listener or, if the notification listener is an MBean, the ObjectName of the notification listener. Note that if you use the version of addNotificationListener( ) or removeNotificationListener( ) that takes an ObjectName as the listener parameter, the specified notification listener MBean must be registered prior to invoking the method. If it isn’t, the MBean server will throw an InstanceNotFoundException (in the case of addNotificationListener( )) or a ListenerNotFoundException (in the case of removeNotificationListener( )). The filter parameter allows you to specify a notification filter, which allows you to enable only certain notifications and send only those notifications to the listener (pass null if no filtering is desired). The handback parameter is a reference to an opaque object to be passed unchanged by the broadcaster to the listener when the broadcaster sends a notification. Because the object is opaque, it should never be modified by the broadcaster (pass null if no handback is required). Notification filters and handback objects are covered in detail in the next chapter, so we’ll skip that discussion for now—it is not important that you understand the details of notification filters and handback objects right now in order to be able to use these methods of the MBean server. In all of the example code in this chapter, we will pass null for both of these parameters.

Suppose that we want to register interest in receiving notifications from the Queue class, which emits two notifications to alert to potential stall conditions (this is covered in some detail in Chapter 3). The first notification is to alert to the possibility that the queue is stalled because it has been full for longer than the threshold value (set programmatically) and nothing has been removed. This could occur, for example, if all of the consumer threads have crashed, because nothing would be removed from the queue. The second notification is to alert the opposite condition: the queue has been empty for longer than the threshold value and nothing has been added. This could occur, for example, if all of the supplier threads have crashed and no other WorkUnits have been added since the last time the queue was signaled as empty. Also, suppose that we want the Controller to receive the notifications emitted by the Queue. Here is the relevant code:

Controller controller = new Controller(  );
MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String defaultDomain = mbeanServer.getDefaultDomain(  );
ObjectName queueObjName = new ObjectName(defaultDomain + ":name=Queue");
ObjectName controllerObjName = new ObjectName(defaultDomain + ":name=Controller");
try {
  mbeanServer.createMBean(queueObjName);
  mbeanServer.registerMBean(controllerObjName);
  mbeanServer.addNotificationListener(queueObjName, controller, null, null);
  // . . .
} catch (/*all the appropriate exceptions*/) {
  // . . .
}

We could also pass the ObjectName for the Controller instead of its Object reference, because the Controller is also an MBean. The highlighted line above would then be:

               
mbeanServer.addNotificationListener(queueObjName, controllerObjName, null, null);

Now any time a potential stall condition occurs in the Queue, a notification will be broadcast by Queue and sent to Controller. It is then up to Controller to handle the notification and take the appropriate action. We will discuss some strategies for implementing notification listeners in the next chapter. For now, be aware that you can use the MBean server to register interest in receiving notifications with a broadcaster for which you do not have an Object reference. This sort of indirect interaction is what makes the MBean server so powerful. When the distributed services level of the JMX architecture gets completely specified, expect this to play a significant role in enabling distributed notifications across the network.

Query

The MBean server allows you to send it queries that return a subset of the MBeans that reside in its registry. In this section, we will look at how to build and submit queries to the MBean server and what methods are provided to allow you to submit these queries. This section will probably be most useful to those developers who are writing connectors and protocol adaptors, although it does show how to programmatically ask an MBean server for a subset of its registered MBeans, based on parameters to the methods provided by the MBean server.

There are two methods for this purpose, defined as:

public Set queryMBeans(ObjectName name, QueryExp query);
public Set queryNames(ObjectName name, QueryExp query);

The parameters to these methods define two sets of criteria: the name parameter defines the scope of the query, which in turn defines the subset of registered MBeans to which the second parameter, query, will be applied. The ObjectName instance passed as name is a pattern that somewhat resembles a regular expression. However, only two regular-expression metacharacters are recognized:

  • Asterisk (*), which matches zero or more characters

  • Question mark (?), which matches a single character

These metacharacters are used in building the ObjectName that defines the subset of MBeans to which the query parameter is applied. The simplest example is to apply the query parameter to all registered MBeans. The ObjectName instance that is passed would be:

ObjectName name = new ObjectName("*:*");

In this example, we have created an ObjectName pattern that matches all registered MBeans. If we then pass null as the query parameter, the subset of MBeans returned is all of the MBeans registered within the MBean server:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("*:*");
QueryExp query = null;
Set results = mbeanServer.queryMBeans(name, query);
// Now look through all registered MBeans. . .

We could narrow the scope of the query to only domains whose names begin with “My”:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("My*:*");
QueryExp query = null;
Set results = mbeanServer.queryMBeans(name, query);

or a single domain called “MyDomain”:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("MyDomain:*");
QueryExp query = null;
Set results = mbeanServer.queryMBeans(name, query);

Similarly, we can look at all domains and narrow the scope of the query to only those MBeans that have a key property called name that has a value of Queue:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("*:name=Queue,*");
QueryExp query = null;
Set results = mbeanServer.queryMBeans(name, query);
// Now look through all registered MBeans. . .

Tip

If no MBeans are found in the scope of the query, an empty Set object is returned. You can test to see if the Set is empty by calling the isEmpty( ) method, which will return true if there are no ObjectInstance objects in the Set, or by calling size( ) to get a count of the number of ObjectInstance objects in the Set. If the number of MBean ObjectInstance objects is zero, no MBeans were found within the scope of the query.

Once you select the scope, you can apply a query to the selected set of MBeans. Only those MBeans in the scope of the query are considered when applying the query logic. For example, if the scope of the query is set to:

ObjectName scope = new ObjectName(":*");

only those MBeans registered within the default domain will be considered when the subsequent query is applied.

A query is constructed using static methods of the Query class to create instances of QueryExp instances, which represent one or more query expressions. This query is then applied to the entire scope of the query, which is specified by the ObjectName pattern we discussed earlier. For example, to query all MBeans within the designated scope for those MBeans whose numberOfResets attributes is zero, use:

QueryExp = Query.eq(
  Query.attr("numberOfResets"),  // attribute name
  Query.value(0)                 // attribute value
);

The resulting QueryExp instance is now ready to be passed to the queryMBeans( ) method:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("*:*");
QueryExp = Query.eq(
  Query.attr("numberOfResets"),  // attribute name
  Query.value(0)                 // attribute value
);
Set results = mbeanServer.queryMBeans(name, query);

In this example, the scope of the query is all registered MBeans whose numberOfResets attributes have a value of zero.

The Query class provides methods that allow you to build SQL-like QueryExp instances for querying MBeans that are within the scope of the query. Chapter 7 of the JMX 1.0 specification provides an excellent discussion of how to use this facility of the MBean server.

The queryMBeans( ) query returns a Set object that contains a collection of ObjectInstance objects that represent the MBeans that satisfied the query. To view the contents of the Set object, create an Iterator and then look at the resulting ObjectInstance objects one at a time:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("*:*");
QueryExp = Query.match(
  Query.attr("numberOfResets"),  // attribute name
  Query.value(0)                 // attribute value
);
Set results = mbeanServer.queryMBeans(name, query);
Iterator iter = results.iterator(  );
while (iter.hasNext(  )) {
  ObjectInstance obj = (ObjectInstance)iter.next(  );
  // etc. . . .
}

Once you have an MBean’s ObjectInstance, you can call the getObjectName( ) method to return an ObjectName instance that corresponds to the object name of the MBean. A more direct alternative is provided by the MBean server, which allows you to perform the same queries but have the MBean server return a Set of ObjectName instances instead. The mechanics of the query are exactly the same:

MBeanServer mbeanServer = /*obtain through some means. . .*/
ObjectName scope = new ObjectName("*:*");
QueryExp = Query.match(
  Query.attr("numberOfResets"),  // attribute name
  Query.value(0)                 // attribute value
);
Set results = mbeanServer.queryNames(name, query);
Iterator iter = results.iterator(  );
while (iter.hasNext(  )) {
  ObjectName objName = (ObjectName)iter.next(  );
  // etc. . . .
}

This query will return a Set object that contains the object names of all the MBeans that matched the query. If you need to use the object name of an MBean found in the query to invoke a method or retrieve or set an attribute of the MBean, this is the method you will probably want to use, as it provides you with more direct access to the ObjectName instance corresponding to the MBean.

Utility

The MBeanServer interface provides eight methods that allow you to access information about the MBeans that are registered. Three of these methods help you to deserialize the state of an MBean from a byte array.

These helper methods are defined as follows:

public ObjectInstance getObjectInstance(ObjectName name)
  throws InstanceNotFoundException;
  
public boolean isRegistered(ObjectName name);
  
public Integer getMBeanCount(  );
  
public String getDefaultDomain(  );
  
public boolean isInstanceOf(ObjectName name, String className)
  throws InstanceNotFoundException;
  
public ObjectInputStream deserialize(ObjectName name, byte[] data)
  throws InstanceNotFoundException, OperationsException;
  
public ObjectInputStream deserialize(String className, byte[] data)
  throws OperationsException, ReflectionException;
  
public ObjectInputStream deserialize(String className, ObjectName loaderName,
                                     byte[] data)
  throws InstanceNotFoundException, OperationsException, ReflectionException;

These methods allow you to:

  • Retrieve an ObjectInstance object that corresponds to an ObjectName, provided the MBean identified by the ObjectName has been registered.

  • Determine whether or not a particular MBean is registered.

  • Obtain the number of MBeans across all domains that have been registered.

  • Obtain a String containing the name of the default domain.

  • Determine whether or not a particular MBean is an instance of a particular class.

  • Create and return an ObjectInputStream object from which primitive types and objects can be read. This method has three versions that provide a number of ways to specify the ClassLoader to be used.

Let’s look at each of these methods. First, there is getObjectInstance( ) , which takes the ObjectName of an MBean and returns the corresponding ObjectInstance for that MBean:

MBeanServer mbeanServer = /* obtain through some means . . . */
ObjectName objName = new ObjectName(mbeanServer.getDefaultDomain(  ) + 
                     ":name=Queue");
try {
  ObjectInstance objInst = mbeanServer.getObjectInstance(objName);
} catch (InstanceNotFoundException e) {
  // . . .
}

If the object name is not found (i.e., the MBean is not registered), an InstanceNotFoundException is thrown. In the example above, we are asking the MBean server for the ObjectInstance of the Queue MBean. This method can be useful if, for example, you need to obtain the class name of the MBean registered under the object name you provide to this method.

If you simply need to know whether a particular MBean has been registered, use the isRegistered( ) method, which takes an ObjectName that identifies the MBean. isRegistered( ) returns true if the MBean is registered and false if it is not:

MBeanServer mbeanServer = /* obtain through some means . . . */
ObjectName objName = new ObjectName(mbeanServer.getDefaultDomain(  ) + ":name=Queue");
boolean yesOrNo = mbeanServer.isRegistered(objName);
String isOrNot = (yesOrNo) ? " is" : " is not ";
System.out.println("The MBean " + objName + isOrNot + "registered.");

The getMBeanCount( ) method tells you the total number of MBeans that have been registered across all domains. In other words, if two MBeans have been registered on the default domain and three MBeans have been registered under a different domain, this method will return the number 5.

MBeanServer mbeanServer = /* obtain through some means . . . */
Integer numberOfMBeans = mbeanServer.getMBeanCount(  );
System.out.println("There are " + numberOfMBeans + " registered MBeans.");

We have already used the getDefaultDomain( ) method in previous examples to get a String that contains the name of the default domain. Recall from earlier in this chapter that we can set the name of the default domain to whatever we want when we create the MBean server (see Figure 6-2).

MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(  );
String defaultDomain = mbeanServer.getDefaultDomain(  );
System.out.println("The default domain is '" + defaultDomain + "'.");

The isInstanceOf( ) method allows us to determine whether an MBean is an instance of a particular class with a single method call:

MBeanServer mbeanServer = /* obtain through some means. . . */
String className = "sample.mbeanserver.Queue";
ObjectName objName = new ObjectName(mbeanServer.getDefaultDomain(  ) + ":name=Queue");
boolean yesOrNo = mbeanServer.isInstanceOf(objName, className);
String isOrNot = (yesOrNo) ? " is " : " is not ";
System.out.println("MBean " + objName + isOrNot + "an instance of " + "class " +
                   className + ".");

Finally, the MBean server provides three versions of the deserialize( ) method that help deserialize objects in a byte array by setting up and returning ObjectInputStream. The ObjectInputStream object returned from deserialize( ) is then used to read primitive types and objects by calling the appropriate methods of ObjectInputStream or one of its parent classes. This class is somewhat misleadingly named—the input stream is not actually deserialized; rather, an ObjectInputStream object is returned to the caller, who is then responsible for processing the input stream through the available methods of ObjectInputStream.

Each version of deserialize( ) provides you with a different way to specify the ClassLoader that ObjectInputStream will use to locate and fetch the Java bytecode—i.e., .class files—and create the corresponding Class objects for the objects in the input stream when ObjectInputStream’s readObject( ) method is called. These methods are provided to developers of protocol adaptors and connectors to aid, for example, in deserializing objects whose bytecode files do not reside on the same physical machine as the MBean server.

The first version of deserialize( ) allows you to use a registered MBean as the ClassLoader for classes read from the input stream when readObject( ) is called:

public ObjectInputStream deserialize(ObjectName name, byte[] data)
  throws InstanceNotFoundException, OperationsException;

The MBean specified by the name parameter must also extend the abstract class ClassLoader and override any methods that provide different functionality than that provided by ClassLoader. This method will search the MBean server’s MBean registry for the MBean specified by name. If the MBean does not exist in the registry, an InstanceNotFoundException is thrown. If the data parameter is null or is zero bytes in length, an OperationsException is thrown.

If all goes well, an ObjectInputStream object is created using the byte array specified by data and returned to the caller. The caller is responsible for retrieving objects (or primitive types) from the ObjectInputStream by calling the appropriate methods of ObjectInputStream. Each time an object is read from the stream via a call to readObject( ) on the returned ObjectInputStream object, the MBean class loader specified by name is used to create the corresponding Class object of the object in the stream.

Through JMX instrumentation, the MBean class loader can expose a management interface that allows a management application to keep track of information such as the number of classes loaded, the number of input streams processed, and the number of serialization errors.

The second version of deserialize( ) allows you to specify the class whose class loader is to be used when loading classes for objects in the input stream:

public ObjectInputStream deserialize(String className, byte[] data)
  throws OperationsException, ReflectionException;

The className parameter must be a fully qualified string representation of the class whose class loader is to be used by the ObjectInputStream object. If this parameter is null, an OperationsException is thrown. Each time readObject( ) is called on the returned ObjectInputStream object, this class loader is used to load the class in the stream. If the data parameter is null or is zero bytes in length, an OperationsException is thrown.

If the class specified by className cannot be located, the MBean server will attempt to use one of the loaders in the default repository of class loaders, which is found in the com.sun.management.jmx package in the DefaultLoaderRepository class. If no suitable loader can be found, a ReflectionException is thrown.

If all goes well, an ObjectInputStream object is created, using the byte array specified by data, and returned to the caller. The caller is then responsible for retrieving objects (or primitive types) from the ObjectInputStream by calling the appropriate methods of ObjectInputStream. Each time an object is read from the stream via a call to readObject( ) on the returned ObjectInputStream object, the class loader of the class specified by className is used to retrieve the bytecode for, and create an instance of, the class of the object in the stream.

If you need to provide a class loader that must be manageable, you should instrument that class loader as an MBean and use the first version of deserialize( ).

The third version of deserialize( ) allows you to use a registered MBean to load the specified class, whose class loader is then to be used to load classes from the input stream:

public ObjectInputStream deserialize(String className, ObjectName loaderName,
                                     byte[] data)
  throws InstanceNotFoundException, OperationsException, ReflectionException;

This version of deserialize( ) functions similarly to the second version, in that the className parameter is the fully qualified string representation of a class whose class loader is to be used to load classes for the objects in the input stream. However, the class specified by className is loaded by an MBean, which is specified by the loaderName parameter.

This method is provided as a convenience to developers who must provide their own class loader, when it is not necessary to manage the class loader itself. However, there may be times when it is necessary to manage the class loader that loads className, even though the MBean specified by loaderName is not the class loader that ultimately loads the class of the object in the input stream.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset