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:
Those methods related to instantiation of MBeans, registration of MBeans, or both
Those methods related to manipulating MBeans through the MBean server, rather than through direct references to the MBeans themselves
Those methods related to MBean notifications
Those methods related to retrieving subsets of registered MBeans by querying the MBean server
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.
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
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
String
s (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.
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.
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
ObjectName
s, 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.
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.
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.
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.
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
WorkUnit
s 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.
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. . .
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.
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.