Like standard MBeans, dynamic MBeans must be created and registered with the MBean server. When the MBean server is asked to register a dynamic MBean, however, no introspection is performed. Recall that it is the strict application of the standard MBean design patterns (discussed in Chapter 2)—enforced through introspection—that tells the MBean server what management interface is exposed on an MBean. So, how does a dynamic MBean expose its management interface?
Instead of using a Java interface with the name
“MBean” on it, dynamic MBeans use
metadata classes to expose their management interfaces. They make
that metadata available through an interface called
DynamicMBean
, which must be implemented by all
dynamic MBeans. This interface is shown in Example 3-1.
Example 3-1. The DynamicMBean interface
package javax.management; public interface DynamicMBean { public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException; public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException; public AttributeList getAttributes(String[] attributes); public AttributeList setAttributes(AttributeList attributes); public Object invoke(String actionName, Object params[], String signature[]) throws MBeanException, ReflectionException; public MBeanInfo getMBeanInfo( ); }
Essentially, the DynamicMBean
interface provides a
way for a
management interface to do four things:
Dynamically discover the management interface exposed by the MBean (getMBeanInfo( )).
Retrieve the value of one or more attributes on the management interface (getAttribute( ) and getAttributes( ), respectively).
Set the value of one or more attributes on the management interface (setAttribute( ) and setAttributes( ), respectively).
Invoke an operation on the management interface (invoke( )).
Instances of the appropriate metadata classes are created by the
MBean (usually in the constructor) and added to the
MBeanInfo
instance that is returned by
getMBeanInfo( ). Similarly, the management
application, after invoking getMBeanInfo( ),
uses the metadata classes to discover that interface. In the next
section, we will take a look at how a dynamic MBean uses the metadata
classes to expose its management interface.
Dynamic MBeans tell the MBean server that they are dynamic MBeans by
exposing the DynamicMBean
interface, but it is the
use of the dynamic MBean metadata classes that ties it all together.
In this section, we will look at these classes and how a dynamic
MBean uses them to describe and expose its management interface to
the world.
There are six significant metadata classes:
MBeanInfo
The top-level container of metadata; each MBean requires only one instance of this class to completely describe its management interface.
MBeanAttributeInfo
Each instance of this class provides information about a single attribute.
MBeanParameterInfo
Each instance of this class provides information about a single parameter.
MBeanConstructorInfo
Each instance of this class provides information about a single
constructor and may contain one or more
MBeanParamaterInfo
instances.
MBeanOperationInfo
Each instance of this class provides information about a single
operation and may contain one or more
MBeanParameterInfo
instances.
MBeanNotificationInfo
Each instance of this class contains information about a group of notifications emitted by this MBean.
Each of these classes (except MBeanInfo
) is a
subclass of MBeanFeatureInfo
. Figure 3-1 shows a UML diagram that describes the
multiplicity (or cardinality) between MBeanInfo
and the other metadata classes.
As you can see from Figure 3-1, there is a whole lot of aggregation going on! The aggregation mechanism used by all of the container classes is an array. Each of these classes is discussed in detail below.
This metadata class is used to describe a single attribute that is exposed on an MBean. To describe a single MBean attribute, six essential properties must be set:
The name of the attribute as it appears on the management interface
The string representation of the
Class
object that corresponds to the
attribute’s data type
The description of the attribute as it should appear to a management application
A boolean
property that
indicates whether or not the attribute’s value can
be retrieved and viewed by a management application
A boolean
property that
indicates whether or not the attribute’s value can
be set by a management application
A boolean
property that
indicates whether or not the attribute is a boolean and if its getter
begins with is instead of
get
An MBean creates an instance of this class for each attribute that it
wants to expose, using one of two constructors of
MBeanAttributeInfo
to set its essential
properties. The first constructor is
defined as:
public class MBeanAttributeInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanAttributeInfo(String name, String type, String description, boolean isReadable, boolean isWritable, boolean isIs) { // . . . } // . . . }
The name parameter is the name of the attribute
as it is exposed on the management interface and is a
String
. The name must match exactly what is
expected by the methods on the DynamicMBean
interface implementation to retrieve and set the
attribute’s value.
The type parameter is the fully qualified name
of the class that is the type of the attribute’s
value. If type is an Integer
,
for example, this parameter will have the value
"java.lang.Integer"
. If type
is one of the Java fundamental types (e.g., byte
,
char
, long
), you may use either
the literal string for that fundamental type or the
name
attribute of the TYPE
member variable of the corresponding fundamental type
java.lang
wrapper class. The following example
shows how to use this constructor for the
QueueSize
attribute of Queue
(whose type is int
) by using the literal
string explicitly for type
int
:
// . . .
public Queue(int queueSize) {
super( );
MBeanAttributeInfo att = new MBeanAttributeInfo(
"QueueSize", // name
"int", // type
"Size of the Queue.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
}
// . . .
Here is an example that shows how to use the JDK
wrapper class for
the fundamental type int
to create an
MBeanAttributeInfo
object for the same
QueueSize
attribute of Queue
:
// . . .
public Queue(int queueSize) {
super( );
MBeanAttributeInfo att = new MBeanAttributeInfo(
"QueueSize", // name
Integer.TYPE.getName( ), // type
"Size of the Queue.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
}
// . . .
For each fundamental type, a JDK wrapper class has been provided as
part of the java.lang
package. Each wrapper class
has a
public static
member variable called TYPE
, which is a
java.lang.Class
object that has been initialized
with a literal string that serves as the string representation of the
fundamental type. Table 3-1 shows the
corresponding type parameter values for each
fundamental
type, along with their JDK 1.3 string
representations.
Table 3-1. type parameter values for attributes whose values are of a fundamental type
Notice in Table 3-1 that I don’t
use a String
literal as the
type of fundamental types. Rather, I use the
appropriate TYPE
member variable of the
fundamental type wrapper class. Each fundamental type has a JDK
wrapper class and a corresponding class-level member variable called
TYPE
that is a Class
object
whose name (accessed by the getName( ) method of
Class
) is a String
containing
the name of the fundamental type’s
Class
. The literal values in Table 3-1 are not likely to change, but using the
TYPE
variable of the appropriate fundamental
type’s wrapper class insulates you from changes to
this literal in future JDK versions.
If the attribute type is an
array, the type
string has a different format: left bracket, capital L (i.e.,
“[L”), followed by the fully
qualified name of the attribute class, followed by a semicolon. For
example, if the class is an array of String
objects, the type string of the array is
"[Ljava.lang.String;"
.
For an array
of fundamental types, the format is
different still. For example, if the attribute type is an array of
char
, the type string would
be "[C"
. Notice that there is no L and no
semicolon at the end of the type string. Also
note that the class name is not
“char,” as it would be if the type
were simply a char
. The class names for arrays of
all fundamental types follow this pattern, where a single uppercase
character follows the left bracket. Table 3-2
lists the literal strings that you must pass for each type of array.
Table 3-2. Class name strings for arrays of fundamental types
Attribute type |
Class name literal string |
---|---|
|
" |
|
" |
|
" |
|
" |
|
" |
|
" |
|
" |
|
" |
Creating the
MBeanAttributeInfo
for a one-dimensional
character array
(char[]
) that uses this constructor looks like
this:
// . . .
MBeanAttributeInfo att = new MBeanAttributeInfo(
"StringArray", // name
"[C", // type
"2D String array.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
Creating the MBeanAttributeInfo
for a
one-dimensional double-precision floating point array
(double[]
) that uses this constructor looks like
this:
// . . .
MBeanAttributeInfo att = new MBeanAttributeInfo(
"DoubleArray", // name
"[D", // type
"2D String array.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
What about multi-dimensional arrays? Simply add another left bracket to the beginning of the literal string representing the array.
A two-dimensional String
array
(String[][]
) looks like this:
// . . .
MBeanAttributeInfo att = new MBeanAttributeInfo(
"TwoDStringArray", // name
"[[Ljava.lang.String;", // type
"2D String array.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
A three-dimensional long integer array
(long[][][]
) looks like this:
// . . .
MBeanAttributeInfo att = new MBeanAttributeInfo(
"ThreeDLongArray", // name
"[[[J", // type
"2D String array.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
The final authority on the syntax of array definitions is the Javadoc
for java.lang.Class
. You should consult the
documentation if you run into any problems.
For attributes with
user-defined types, the
type
parameter is the fully qualified class name. For example, suppose
that we have an attribute called ConsumerQueue
from the application’s Controller
class. This attribute’s type is
Queue
. The code to create an
MBeanAttributeInfo
object for this attribute is:
// . . .
MBeanAttributeInfo att = new MBeanAttributeInfo(
"ConsumerQueue", // name
"sample.dynamic.Queue", // type
"2D String array.", // description
true, // isReadable
true, // isWritable
false // isIs
);
// . . .
When using the HTMLAdaptorServer
that ships with
the JMX 1.0 RI (in jmxtools.jar
), user-defined
types
are not supported
for attributes. This may change without notice, however, because
jmxtools.jar
is not technically part of the RI.
The description
parameter is simply a human-readable
description of the attribute and can be anything you want. However,
if you are going to use the HTMLAdaptorServer
from
the JMX 1.0 RI, please note that you cannot use single or double
quotes in the description. This seems to mess up the Adaptor, and you
will not be able to retrieve the description from your browser.
The isReadable parameter
indicates whether or not the
attribute’s value can be retrieved through the
management interface. Set this parameter to true
if the value can be read through the management interface, and
false
otherwise.
The isWritable parameter
indicates whether or not the
attribute’s value can be set through the management
interface. Pass true
if it can be, and
false
otherwise.
If the attribute is a boolean
value and its getter
starts with “is”, pass
isIs as true
. Otherwise, pass
false
.
There is a second
constructor to
MBeanAttributeInfo
that you may find helpful:
public class MBeanAttributeInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanAttributeInfo(String name, String description, java.lang.reflect.Method getter, java.lang.reflect.Method setter) throws IntrospectionException { // . . . } // . . . }
This constructor is provided as a convenience if you prefer to use
the Java
reflection API to obtain
references to the Method
objects for the getter
and setter and then pass those references to the constructor. The
constructor then figures out whether the attribute is readable,
writable, and/or boolean
, and what its type is.
Consequently, you don’t have to worry about
memorizing the information in Tables 3-1 and 3-2.
Suppose that for a particular MBean, we are creating the metadata
classes to represent the attributes inside the constructor of the
MBean. It is simple, then, to get a
reference to the
Method
objects for the getter and setter:
// . . . public Queue(int queueSize) { super( ); Class[] setterSignature = new Class[1]; setterSignature[0] = Integer.TYPE; try { Method getter = this.getClass( ).getMethod("getQueueSize", null); Method setter = this.getClass( ).getMethod("setQueueSize", setterSignature); MBeanAttributeInfo att = new MBeanAttributeInfo( "QueueSize", "Size of the Queue.", getter, setter ); } catch (NoSuchFieldException e) { // oops, shouldn't really get this unless we mistyped the name e.printStackTrace( ); } catch (SecurityException e) { // you don't have access to the method. . . e.printStackTrace( ); } catch (IntrospectionException e) { // something has gone horribly wrong. . . e.printStackTrace( ); } // . . . } // . . .
If the attribute is read-only, pass null
as the
setter parameter. Conversely, if the attribute
is write-only, pass null
as the
getter parameter. This approach works well if
the attribute’s name is likely to be more static
than its type. If the name never changes, the code above will not
need to change, because the MBeanAttributeInfo
constructor uses Java’s reflection API to determine
the attribute’s
type.
Three essential properties of a parameter to an MBean constructor or operation must be set in order to describe that parameter:
Each parameter to a constructor or operation must be described using
the MBeanParameterInfo
class. Each instance of
this class serves to completely describe a single parameter to a
single constructor or operation. Like
MBeanAttributeInfo
objects, the way to create
instances of MBeanParameterInfo
is to use its
custom constructors. In fact, there is only one
constructor for
MBeanParameterInfo
:
public class MBeanParameterInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanParameterInfo(String name, String type, String description) { // . . . } // . . . }
The name parameter is the name of the parameter
as it appears to the management application. It is good style for
this name to match the name in the source code, but the RI
doesn’t seem to care what you use for this
parameter, including an empty string
(“”) or a null
reference. Note, however, that if you pass a
null
reference for this
parameter, a
NullPointerException
will be thrown by the HTMLAdaptorServer
when you
try to access any MBean that uses this
MBeanParameterInfo
instance. The MBean server, on
the other hand, will happily register the MBean without any glitches.
This behavior indicates that Version 1.0 of the
HTMLAdaptorServer
doesn’t much
care for a null
reference for the
name parameter.
The type parameter for
MBeanParameterInfo
is exactly the same as the
type parameter for
MBeanAttributeInfo
. All of the information about
the type parameter in the previous section
applies here as well.
The description parameter is simply a
human-readable description of the attribute and can be anything you
want. However, if you are going to use the
HTMLAdaptorServer
from the JMX 1.0 RI, note that
you cannot use single or double quotes in the description. This seems
to mess up the Adaptor, and you will not be able to retrieve the
description from your browser.
It’s time for an example from the application. The
Controller
class has a management operation called
createNewWorker( ) that is used to start another
worker thread. In order to do this, however, the
worker’s role (a String
) and the
work factor (the amount of work the worker is to perform for each
work unit) must be specified. An instance of
MBeanParameterInfo
will be created for each of
these parameters:
// . . . MBeanParameterInfo param1 = new MBeanParameterInfo( "role", // name "java.lang.String", // type "The role this new worker thread will take on." // description ); MBeanParameterInfo param2 = new MBeanParameterInfo( "workFactor", Integer.TYPE.getName( ), "The weighted work factor for this new worker thread." ); // . . .
As I stated earlier, you are not required to supply a
name parameter to the constructor of
MBeanParameterInfo
. In fact, you
don’t have to supply a description either. Thus, you
don’t have to create different instances of
MBeanParameterInfo
for parameters that can be
described in exactly the same way. In other words, if parameters have
at a minimum the same type, they can share the same instance of
MBeanParameterInfo
. Be careful when doing this,
however, because parameters can have the same type but mean different
things to a management application. Also, it may confuse the person
trying to manage your MBeans if he is relying on either the name or
description of an operation or constructor’s
parameters to meaningfully manage the resources within your
application.
Three essential properties must be set in order to describe an MBean constructor:
The MBeanConstructorInfo
metadata class is used to
describe an MBean’s public constructors. One
instance of this class completely describes a single constructor. In
other words, if an MBean has three public constructors, three
instances of this class are required to completely describe the
MBean’s constructors. Like the other metadata
classes, MBeanConstructorInfo
provides two custom
constructors that are used to set various properties of this object.
The first constructor is defined as:
public class MBeanConstructorInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanConstructorInfo(String description, java.lang.reflect.Constructor constructor) { } // . . . }
This constructor uses Java’s reflection API
to figure out what the constructor’s parameters are
and creates MBeanParameterInfo
objects for them
accordingly. However, when you use this method, you will not be able
to specify a name for the constructor or a description for the
constructor’s parameters.
This constructor is very easy to use if you want to expose all of the constructors for an MBean. For example, the following code will expose all of the public constructors of the class of which this is an instance:
// . . . Constructor[] constructors = this.getClass().getConstructors( ); MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[constructors.length]; for (int aa = 0; aa < constructors.length; aa++) { constructorInfo[aa] = new MBeanConstructorInfo( "Constructs a Basic MBean.", // description constructors[aa] // java.lang.reflect.Constructor ); } // . . .
The getConstructors(
)
method of the Class
object associated with this
returns an array of
java.lang.reflect.Constructor
objects. The size of
the array is a value equal to the number of public constructors the
class contains, and this value is used to create an equally sized
array of MBeanConstructor
objects. Then each
Constructor
object is used to create a
corresponding MBeanConstructor
instance.
Alternately, you can expose a specific constructor by creating an
MBeanConstructorInfo
object for it. Example 3-2 shows how to do this.
Example 3-2. Exposing a specific constructor
public class Queue extends Basic implements DynamicMBean { // . . . public Queue(int QueueSize) { // . . . Class[] signature = {Integer.TYPE}; Constructor constructor = null; MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1]; try { constructor = this.getClass( ).getConstructor(signature); constructorInfo[0] = new MBeanConstructorInfo( "Queue custom constructor", // description constructor // java.lang.reflect.Constructor ); } catch (Exception e) { e.printStackTrace( ); return; } // . . . } // . . . }
In this example, we explicitly exposed a single constructor whose
signature consists of a single int
parameter. The
getConstructor( ) method of
Class
takes a Class
array that
contains the Class
objects that match the
signature of the constructor we want to retrieve. If the constructor
is not found, a
NoSuchMethodException
is thrown. If the constructor is found, it’s a
simple matter of creating a new
MBeanConstructorInfo
object, passing the
Constructor
object we retrieved earlier.
Suppose that we have another constructor that takes an
Integer
parameter,
instead of a fundamental int
. How do we get a
Class
object for an Integer
?
There is a static method of Class
called
forName( ) that takes a
String
(actually, there are two versions of this
method, but we’ll use the simpler of the two), which
is the fully qualified name of the class for which we want a
Class
object. This method does throw an exception
if the class can’t be found, so we have to surround
our code with try/catch
blocks. Using this
scenario, Example 3-2 becomes:
public class Queue extends Basic implements DynamicMBean {
// . . .
public Queue(int QueueSize) {
// . . .
Class[] signature = new Class[1];
Constructor constructor = null;
MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
try {
signature[0] = Class.forName("java.lang.Integer");
constructor = this.getClass( ).getConstructor(signature);
constructorInfo[0] = new MBeanConstructorInfo(
"Queue custom constructor", // description
constructor // java.lang.reflect.Constructor
);
} catch (Exception e) {
e.printStackTrace( );
return;
}
// . . .
}
// . . .
}
The second constructor of MBeanConstructorInfo
requires a little more effort on our part, but it allows us to
provide a name and description for each parameter of the constructor
we expose. This can be helpful to the operator of a management
application, as this information will be exposed if this constructor
is used. The constructor is defined as:
public class MBeanConstructorInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanConstructorInfo(String name, String description, MBeanParameterInfo[] signature) { } // . . . }
The extra work required on our part is that we must create an array
of MBeanParameterInfo
objects that correspond to
the signature of the constructor. This is really not a big deal,
though; we saw how to create MBeanParameterInfo
objects in the previous section. Suppose that we want to expose the
constructor from Example 3-2, which takes a single
int
parameter. In that case, we create a single
MBeanParameterInfo
object and pass it to the
constructor of MBeanConstructorInfo
:
public class Queue extends Basic implements DynamicMBean { // . . . public Queue(int QueueSize) { // . . . MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1]; MBeanParameterInfo[] parms = new MBeanParameterInfo[1]; parms[0] = new MBeanParameterInfo( "queueSize", Integer.TYPE.getName( ), "Max number of items the Queue may contain at any time." ); constructorInfo[0] = new MBeanConstructorInfo( "Queue", "Queue custom constructor", parms ); // . . . } // . . . }
Notice how we explicitly create an instance of
MBeanParameterInfo
to describe the
int
parameter to our constructor. This object is
then placed into an array consisting of a single
MBeanParameterInfo
element, which is passed to the
second constructor of MBeanConstructorInfo
.
If you are exposing the default constructor, you have two options.
The first option is to simply pass null
as the
third parameter:
// . . .
MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
constructorInfo[0] = new MBeanConstructorInfo(
"Queue",
"Default Constructor",
null
);
// . . .
A second, arguably more readable, way to expose the default
constructor is to pass an empty array of
MBeanParameterInfo
objects:
// . . .
MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
constructorInfo[0] = new MBeanConstructorInfo(
"Queue",
"Default Constructor",
new MBeanParameterInfo[0]
);
// . . .
Five essential properties of an MBean operation must be set when describing the operation:
The name of the constructor as it appears to a management application
The description of the constructor as it should appear to a management application
An array of MBeanParameterInfo
objects that describe the constructor’s signature
An indicator of the type of impact to the state of the MBean following an invocation of the operation
MBeanOperationInfo
is used to describe the
operations that are exposed on an MBean’s management
interface. One instance of this class is required to completely
describe a single operation. In other words, if an MBean exposes four
operations on its management interface, four instances of this class
are required in order to describe the operations on the management
interface of the MBean. Like the other metadata classes,
MBeanOperationInfo
uses its
constructors to set its five essential
properties. The first constructor is defined as:
public class MBeanOperationInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanOperationInfo(String description, java.lang.reflect.Method method) { // . . . } // . . . }
This constructor uses Java’s
reflection API to figure out what the
constructor’s parameters (and their types) are and
creates MBeanParameterInfo
objects for them
accordingly. In addition, the return type of the operation is
discovered through reflection. The impact
property (see above) cannot be discovered, however, because there is
no way to determine the impact of invoking the MBean operation
through the reflection API; the impact of invoking the operation on
the MBean’s state is unknown, so this constructor
sets impact to
MBeanParameterInfo.UNKNOWN
.
When you use this method, you will not be able to specify a name or a description for the constructor’s parameters.
You must obtain a reference to the
java.lang.reflect.Method
object for the operation
you wish to expose:
public class Controller extends Basic implements DynamicMBean { // . . . public Controller( ) { // . . . MBeanOperationInfo[] operationInfo = new MBeanOperationInfo[1]; Method operation = null; Class[] parms = new Class[2]; try { parms[0] = Class.forName("java.lang.String"); parms[1] = Integer.TYPE; operation = this.getClass( ).getMethod("createWorker",parms); operationInfo[0] = new MBeanOperationInfo( "createWorker", operation ); } catch (Exception e) { e.printStackTrace( ); return; } // . . . } // . . . // operation to be exposed on the management interface public void createWorker(String workerType, int workFactor) { // . . . } }
In this example, we are exposing an operation called
createWorker( ) that takes two parameters: a
String
and an int
. To expose an
operation using the reflection-based constructor of
MBeanParameterInfo
, we must accomplish the
following:
Obtain Class
objects that represent the proper
Class
objects for the types of signatures the
MBean operation has. In this example, the first parameter is a
String
and the second is an
int
. We can obtain a String
Class
object by using the static
forName( ) method of Class
.
For the fundamental type int
, we simply use the
TYPE
member, which is a Class
object that represents an int
:
// . . . public Controller( ) { // . . . Method operation = null; Class[] parms = new Class[2]; try { parms[0] = Class.forName("java.lang.String"); parms[1] = Integer.TYPE; operation = this.getClass( ).getMethod("createWorker",parms); // . . . // . . . } // . . .
Obtain a reference to the
Method
object that corresponds to the operation we want to expose. To do
this, we must use the Class
object of the MBean
object itself. Once we obtain the Class
object, we
can use its getMethod( ) method to retrieve the
MBean operation’s Method
object.
Because getMethod(
)
may throw an exception if the method is not
found, we must use try/catch
blocks:
// . . .
public Controller( ) {
// . . .
try {
parms[0] = Class.forName("java.lang.String");
parms[1] = Integer.TYPE;
operation = this.getClass( ).getMethod("createWorker",parms);
operationInfo[0] = new MBeanOperationInfo(
"createWorker",
operation
);
} catch (Exception e) {
e.printStackTrace( );
return;
}
// . . .
}
Create the MBeanOperationInfo
object:
// . . . public Controller( ) { // . . . try { parms[0] = Class.forName("java.lang.String"); parms[1] = Integer.TYPE; operation = this.getClass( ).getMethod("createWorker",parms); operationInfo[0] = new MBeanOperationInfo( "createWorker", operation ); } catch (Exception e) { e.printStackTrace( ); return; } // . . . } // . . .
The second constructor of MBeanOperationInfo
allows you to explicitly specify all of the essential properties of
an MBean operation and is defined as:
public class MBeanOperationInfo extends MBeanFeatureInfo implements java.io.Serializable, Cloneable { // . . . public MBeanOperationInfo(String name, String description, MBeanParameterInfo[] signature, String type, int impact) { // . . . } // . . . }
This constructor is arguably easier to use than its reflection-based
counterpart, because it’s so easy to create
MBeanParameterInfo
objects:
public class Controller extends Basic implements DynamicMBean { // . . . public Controller( ) { // . . . MBeanOperationInfo[] operationInfo = new MBeanOperationInfo[1]; MBeanParameterInfo[] parms = new MBeanParameterInfo[2]; parms[0] = new MBeanParameterInfo( "role", "java.lang.String", "The role this new worker thread will take on." ); parms[1] = new MBeanParameterInfo( "workFactor", Integer.TYPE.getName( ), "The work factor for this new worker thread." ); operationInfo[0] = new MBeanOperationInfo( "createWorker", "Creates a new worker thread.", parms, Void.TYPE.getName( ), MBeanOperationInfo.ACTION ); // . . . } // . . . // operation to be exposed on the management interface public void createWorker(String workerType, int workFactor) { // . . . } }
In this example, we create an instance of
MBeanParameterInfo
for each of the parameters to
the operation we want to expose and place the instances into an
array. We then pass this array to the second constructor of
MBeanOperationInfo
.
Notice the
impact property, which is the fifth parameter to
the constructor (we used
MBeanOperationInfo.ACTION
). Four values for
impact
are defined on MBeanOperation
:
INFO
The state of the MBean will remain unchanged as a result of invoking this operation, because the operation will only return information.
ACTION
The state of the MBean will be changed in some way as a result of invoking this operation. This could be as simple as an internal value changing (i.e., something not on the management interface as an attribute) or as complex as the externally visible state of the MBean changing.
ACTION_INFO
This operation will return some information about the MBean, and the
state of the MBean will change as a result of invoking this
operation. This is a combination of INFO
and
ACTION
.
UNKNOWN
This value indicates that the impact of invoking the method is not
known. When you use the reflection-based constructor of
MBeanOperationInfo
, this is the value to which
impact is
set.
There are three essential properties of an MBean notification:
MBeanNotificationInfo
is the metadata class used
by an MBean to indicate to the MBean server what notification types
the MBean emits (we will discuss the JMX notification model in
greater detail in Chapter 6). One instance of this
class is necessary to completely describe a single group of
notifications, but what exactly is a “group of
notifications?” A group of
notifications is made up of one or more
notifications of the same general type. It is up to you to define
what notifications belong together, depending upon the different
types of notifications that are emitted by the application resource
the MBean represents.
Like the other metadata classes, the essential properties for
MBeanNotificationInfo
are set by calling its
single constructor, which is defined as:
public class MBeanNotificationInfo extends MBeanFeatureInfo implements Cloneable, java.io.Serializable { // . . . public MBeanNotificationInfo(String[] notifsType, String name, String description) { // . . . } // . . . }
We are already familiar with the name and
description parameters, but what about
notifsType? This constructor parameter is a
String
array that contains a group of
notifications that the MBean will emit. A notification is a
String
of the form:
vendor
[.application
][.component
][.eventGroup
].event
where vendor
is the name of your company,
application
is the name of the application
(optional), component
is the name of the
component (usually the name of the MBean, also optional),
eventGroup
is the name of the group to
which the event belongs (optional), and
event
is the name of the event
notification. The minimal notification String
should contain vendor
and
event
, but I encourage you to be as
detailed as possible when naming the notifications your MBeans will
emit.
For example, suppose we define a group of notifications for the
Queue
class from the sample application. This
group of notifications will be for potential stall conditions, where
for some reason the queue appears to be
“asleep.” Say we define one
notification for the condition when the queue is continuously full
for more than a preset amount of time. This may indicate that the
application has stalled, perhaps by a consumer thread crashing. Then
let’s define another notification for the condition
when the queue is continuously empty for longer than a preset period
of time. This may indicate that a supplier thread has crashed. These
notifications will be defined as
"sample.Queue.stalled.Full"
and
"sample.Queue.stalled.Empty"
.
In this case, sample
is the vendor, and we have
omitted the optional application
from the
notification names. Both of these notifications indicate a potential
stall condition in the queue. Now let’s create an
instance of MBeanNotificationInfo
:
public class Queue extends Basic implements DynamicMBean { // . . . public static final String NOTIF_STALLED_FULL = "sample.Queue.stalled.full"; public static final String NOTIF_STALLED_EMPTY = "sample.Queue.stalled.empty"; // . . . public Queue(int QueueSize) { // . . . String[] notificationTypes = new String[2]; notificationTypes[0] = NOTIF_STALLED_FULL; notificationTypes[1] = NOTIF_STALLED_EMPTY; MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[1]; notificationInfo[0] = new MBeanNotificationInfo( notificationTypes, "StalledQueueNotifications", "Potential stall notifications emitted by the Queue." ); // . . . } // . . . }
We declared the notification String
s as constants
on the class because these literal strings will be needed by the part
of the Queue
that actually emits the notifications
and can be referenced everywhere by their variable names. As you can
see from this example, creating an
MBeanNotificationInfo
object for a group of
notifications is quite straightforward.
The convention I suggest here regarding the use of a single instance
of MBeanNotificationInfo
to group similar
notifications is purely my own. It’s not mentioned
in the JMX specification; it just seems to me to be a good idea.
Strictly speaking, you don’t have to use this idiom.
For example, we could have created an instance of
MBeanNotificationInfo
for each notification the
MBean will emit:
public class Queue extends Basic implements DynamicMBean { // . . . public static final String NOTIF_STALLED_FULL = "sample.Queue.stalled.full"; public static final String NOTIF_STALLED_EMPTY = "sample.Queue.stalled.empty"; // . . . public Queue(int QueueSize) { // . . . String[] notificationTypes = new String[1]; notificationTypes[0] = NOTIF_STALLED_FULL; MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[2]; notificationInfo[0] = new MBeanNotificationInfo( notificationTypes, "StalledFullQueueNotification", "Potential stall notifications emitted when the Queue is full." ); notificationTypes = new String[1]; notificationTypes[0] = NOTIF_STALLED_EMPTY; notificationInfo[1] = new MBeanNotificationInfo( notificationTypes, "StalledEmptyQueueNotification", "Potential stall notifications emitted when the Queue is empty." ); // . . . } // . . . }
This is a perfectly reasonable approach to use for a small number of notifications. However, if your MBean emits a lot of notifications, you may want to consider grouping them and taking the approach shown earlier.
There are six essential properties of an MBean:
A single instance of MBeanInfo
is sufficient to
completely describe the management interface for an MBean, because
the getters provided by MBeanInfo
allow a
management application to drill down into, and retrieve, all of the
metadata for an MBean. Think of this class as the
“magic cookie” that the distributed
services level of the JMX architecture uses to expose a dynamic
MBean’s management interface to a management
application.
When getMBeanInfo( )
is invoked on a dynamic MBean, information
in the form of metadata is returned to the caller. The metadata is
contained in an instance of a class called
MBeanInfo
, which is at the top of the dynamic
MBean metadata hierarchy. It is through this metadata that the
interface is both exposed by the MBean and discovered by the
management application. This class should be created last, because
all of the attributes, parameters, constructors, operations, and
notifications must be completely described before an instance of
MBeanInfo
can be created.
Like all of the other metadata classes, the
MBeanInfo
constructor provides a way to set the
essential properties. This means, of course, that you must create the
metadata class instances first so that they are available at the time
you create the MBeanInfo
instance. Once instances
of the appropriate MBeanAttributeInfo
,
MBeanConstructorInfo
,
MBeanOperationInfo
, and
MBeanNotificationInfo
have been created, they can
be added to MBeanInfo
.
The constructor of the
MBeanInfo
class looks
like this:
public class MBeanInfo implements Cloneable, java.io.Serializable { // . . . public MBeanInfo(String className, String description, MBeanAttributeInfo[] attributes, MBeanConstructorInfo[] constructors, MBeanOperationInfo[] operations, MBeanNotificationInfo[] notifications) { // . . . } // . . . }
Now it’s time for the metadata classes describing
the attributes, constructors, operations, and notifications to be
added to MBeanInfo
. The way to do this is through
MBeanInfo
’s constructor. You may
have noticed in the earlier examples that we created an array of the
metadata objects and then added the objects to the array. Once we
create the array of metadata objects, we can simply pass the arrays
to the MBeanInfo
constructor.
Example 3-3 shows how to create the
MBeanInfo
class,
using the other metadata classes. This example ties together all of
the previous discussion about metadata classes.
Example 3-3. Creating an instance of MBeanInfo
public class Queue extends Basic implements DynamicMBean { // . . . private MBeanInfo _MBeanInfo; // . . . public static final String NOTIF_STALLED_FULL = "sample.Queue.stalled.full"; public static final String NOTIF_STALLED_EMPTY = "sample.Queue.stalled.empty"; // . . . public Queue(int queueSize) { // . . . // Attributes attributeInfo[0] = new MBeanAttributeInfo( "QueueSize", Integer.TYPE.getName( ), "Maximum number of items the queue may contain at one time.", true, true, false); attributeInfo[1] = new MBeanAttributeInfo( "NumberOfConsumers", Integer.TYPE.getName( ), "The number of consumers pulling from this Queue.", true, false, false); attributeInfo[2] = new MBeanAttributeInfo( "NumberOfSuppliers", Integer.TYPE.getName( ), "The number of suppliers supplying to this Queue.", true, false, false); attributeInfo[3] = new MBeanAttributeInfo( "QueueFull", Boolean.TYPE.getName( ), "Indicates whether or not the Queue is full.", true, false, true); attributeInfo[4] = new MBeanAttributeInfo( "QueueEmpty", Boolean.TYPE.getName( ), "Indicates whether or not the Queue is empty.", true, false, true); attributeInfo[5] = new MBeanAttributeInfo( "Suspended", Boolean.TYPE.getName( ), "Indicates whether or not the Queue is currently suspended.", true, false, true); attributeInfo[6] = new MBeanAttributeInfo( "EndOfInput", Boolean.TYPE.getName( ), "Indicates if end-of-input has been signalled by all suppliers.", true, false, true); attributeInfo[7] = new MBeanAttributeInfo( "NumberOfItemsProcessed", Long.TYPE.getName( ), "The number of items that have been removed from the Queue.", true, false, false); attributeInfo[8] = new MBeanAttributeInfo( "AddWaitTime", Long.TYPE.getName( ), "No. Milliseconds spent waiting to add because Queue was full.", true, false, false); attributeInfo[9] = new MBeanAttributeInfo( "RemoveWaitTime", Long.TYPE.getName( ), "No. milliseconds spent waiting to remove because Queue was empty.", true, false, false); // Constructors Class[] signature = {Integer.TYPE}; Constructor constructor = null; MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1]; try { constructor = this.getClass( ).getConstructor(signature); constructorInfo[0] = new MBeanConstructorInfo( "Custom constructor", constructor); } catch (Exception e) { e.printStackTrace( ); } // Operations MBeanOperationInfo[] operationInfo = new MBeanOperationInfo[3]; MBeanParameterInfo[] parms = new MBeanParameterInfo[0]; operationInfo[0] = new MBeanOperationInfo( "suspend", "Suspends processing of the Queue.", parms, Void.TYPE.getName( ), MBeanOperationInfo.ACTION); operationInfo[1] = new MBeanOperationInfo( "resume", "Resumes processing of the Queue.", parms, Void.TYPE.getName( ), MBeanOperationInfo.ACTION); operationInfo[2] = new MBeanOperationInfo( "reset", "Resets the state of this MBean.", parms, Void.TYPE.getName( ), MBeanOperationInfo.ACTION); // Notifications MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[1]; String[] notificationTypes = new String[2]; notificationTypes[0] = NOTIF_STALLED_FULL; notificationTypes[1] = NOTIF_STALLED_EMPTY; notificationInfo[0] = new MBeanNotificationInfo( notificationTypes, "StalledQueueNotifications", "Potential stall notifications emitted by the Queue." ); // MBeanInfo _MBeanInfo = new MBeanInfo( "Queue", "Queue MBean", attributeInfo, constructorInfo, operationInfo, notificationInfo ); } // . . . }
In this example, 10 attributes, 1 constructor, 3 operations, and 2
notifications describe the Queue
as a managed
resource. Notice the private variable _MBeanInfo
,
which is used to hold an instance of the MBeanInfo
object that contains the metadata for the Queue
MBean. This variable is returned by the getMBeanInfo( )
method(part of the
DynamicMBean
interface) that is invoked by a
management application to discover the management interface of an
MBean.
In this section, we looked at how to create all of the metadata
classes necessary to fully describe the management interface of a
dynamic MBean. In the next section, we will take a closer look at how
the information in the MBeanInfo
object must match
up to the logic in the implementation of the
DynamicMBean
interface.
Example 3-1 showed the definition of the
DynamicMBean
interface. In this section, we will
first look at some other support classes from the
javax.management
package that are important to
properly implementing dynamic MBeans. Then we will look at how to
write code to implement the methods on the dynamic MBean interface.
This class is used to encapsulate a single attribute value. There are two important properties of this class, name and value, which represent the attribute’s name and value, respectively. The following code shows the significant features of this class:
package javax.management; // . . . public class Attribute implements Serializable { // . . . public Attribute(String name, Object value) { // . . . } // . . . public String getName( ) { // . . . } public Object getValue( ) { // . . . } // . . . }
There are three significant features to this class:
When creating the class, pass in the name of
the attribute and an object reference for the
attribute’s value. If the
attribute’s type is one of the Java fundamental
types, wrap that fundamental type in the appropriate JDK wrapper
class (e.g., if the type is a char
, wrap it in an
Character
object).
Returns the attribute’s name.
Returns the attribute’s value.
Instances of
this class are used to hold a
List
of Attribute
instances.
The following code shows the significant features of
AttributeList
:
package javax.management; import java.util.ArrayList; // . . . public class AttributeList extends ArrayList { public AttributeList( ) { } // . . . }
AttributeList
inherits from
ArrayList
, which means that all of the methods you
are used to using for an ArrayList
—such as
get( ), set( ), and
iterator( )—are available on
AttributeList
(see the JDK Javadoc for more
information).
To create an instance of AttributeList
, use:
AttributeList attributeList = new AttributeList( );
To set the initial capacity of the internal
ArrayList
to 10, use:
AttributeList attributeList = new AttributeList(10);
To add an Attribute
to the
AttributeList
, use:
Attribute attribute = new Attribute("QueueSize", new Integer(10));
AttributeList attributeList = new AttributeList( );
attributeList.add(attribute);
AttributeList
is an ArrayList
,
so there are two ways of getting at the contents of any
AttributeList
instance. The first way is to use a
java.util.Iterator
object:
// assume attributeList is an instance of AttributeList Iterator iter = attributeList.iterator( ); while (iter.hasNext( )) { Attribute attribute = (Attribute)iter.next( ); // do something with attribute. . . }
The second way is to access the contents using an
index. The code snippet below shows how
to walk through the contents of the AttributeList
from beginning to end. However, you can also access any member of the
AttributeList
directly by specifying its index (as
long as you don’t specify an invalid index, in which
case an IndexOutOfBoundsException
will be thrown).
// assume attributeList is an instance of AttributeList for (int idx = 0; idx < attributeList.size( ); idx++) { Attribute attribute = (Attribute)attributeList.get(idx); // do something with attribute. . . }
Now, let’s take the example of the
Queue
class from the sample application. We have
already looked at Queue
’s
management interface and how we created the necessary metadata
classes to expose it. Here we will look at how we implement
DynamicMBean
on the
Queue
class so that the
management interface functionally corresponds to how it is exposed.
The getMBeanInfo( ) method of the
DynamicMBean
interface is what ties the metadata
to the implementation of the DynamicMBean
interface. This is a very simple method that simply returns an
MBeanInfo
instance that contains the definition of
the management interface of the MBean. A management application uses
getMBeanInfo( ) to discover what the management
interface looks like. But then what? Well, now that the management
interface is known, the management application uses the
DynamicMBean
interface to set and get attribute
values and invoke methods on the MBean.
Let’s take a closer look at the methods of the
DynamicMBean
interface and how to implement them.
This method is used to retrieve a single attribute value and is defined as:
public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { // . . . }
Recall from Section 3.2.1 that every attribute must
have a corresponding MBeanAttributeInfo
instance
to describe it. One of the required parameters to both of the
constructors of MBeanAttributeInfo
is
name, which is the name of the attribute. This
name is passed to getAttribute( ) as a
String
. If this parameter does not match one of
the attribute names from the metadata, you must throw an
AttributeNotFoundException
. The following example
shows one such implementation. When looking at this example, you may
want to refer back to Example 3-3, which shows the
metadata created for the attributes of the Queue
class, so you can see how the attribute names there must match up
with the code in getAttribute( ).
public class Queue extends Basic implements DynamicMBean {
// . . .
public Object getAttribute(String attributeName)
throws AttributeNotFoundException,
MBeanException,
ReflectionException {
Object ret = null;
if (attributeName.equals("QueueSize")) {
ret = new Integer(getQueueSize( ));
}
else if (attributeName.equals("NumberOfSuppliers")) {
ret = new Integer(getNumberOfSuppliers( ));
}
else if (attributeName.equals("NumberOfConsumers")) {
ret = new Integer(getNumberOfConsumers( ));
}
else if (attributeName.equals("QueueFull")) {
ret = new Boolean(isQueueFull( ));
}
else if (attributeName.equals("QueueEmpty")) {
ret = new Boolean(isQueueEmpty( ));
}
else if (attributeName.equals("Suspended")) {
ret = new Boolean(isSuspended( ));
}
else if (attributeName.equals("EndOfInput")) {
ret = new Boolean(isEndOfInput( ));
}
else if (attributeName.equals("NumberOfItemsProcessed")) {
ret = new Long(getNumberOfItemsProcessed( ));
}
else if (attributeName.equals("AddWaitTime")) {
ret = new Long(getAddWaitTime( ));
}
else if (attributeName.equals("RemoveWaitTime")) {
ret = new Long(getRemoveWaitTime( ));
}
else throw new AttributeNotFoundException(
"Queue.getAttribute( ): ERROR: " +
"Cannot find " + attributeName + " attribute.");
}
return ret;
}
// . . .
}
The code itself is very simple: it’s just a large
if/else
if/else
construct in
which we attempt to match the attribute name that is passed as a
parameter. If the attribute name matches, we simply call the
appropriate getter, wrap any fundamental types in their appropriate
JDK wrappers, and return the value to the caller. If the attribute
value cannot be found, we throw an
AttributeNotFoundException
with some information
about the requested attribute.
What if your management interface has no attributes? In that case,
the body of this method should throw an
AttributeNotFoundException
, because there are no
attributes. For example, if Queue
has no
attributes, this method will look like this:
public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { throw AttributeNotFoundException("Attribute'" + attribute + "' not found."); }
This method is used to set a single attribute value and is defined as:
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { // . . . }
What is passed to this class is an Attribute
instance, a JMX class that wraps an attribute’s name
and value as a pair. Attribute
has two getters:
one for the name and one for the value. The name must match one of
the attribute names that has been exposed on the management interface
of your MBean, or an
AttributeNotFoundException
will be thrown.
public class Queue extends Basic implements DynamicMBean { // . . . public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { String name = attribute.getName( ); Object value = attribute.getValue( ); if (name.equals("QueueSize")) { if (value instanceof Integer) setQueueSize(((Integer)value).intValue( )); else { throw new InvalidAttributeValueException( "QueueSize must be an Integer." ); } } else throw new AttributeNotFoundException( "Queue.setAttribute( ): ERROR: " + "Cannot find attribute '" + name + "'."); } } // . . . }
Queue
has only one writable attribute,
QueueSize
, so that’s the only one
we have to handle. But notice that we were careful to make sure the
object passed inside the Attribute
instance is of
the correct type—if it isn’t, we throw an
InvalidAttributeValueException
.
This is standard practice when setting attribute values inside
setAttribute( ) and prevents all sorts of nasty
runtime exceptions from the JVM. Because it is possible that an
ill-mannered management application may ignore the information in the
metadata and pass an attribute value of the wrong type, we should
always be careful and code for this contingency.
This method is used to retrieve the values of a group of attributes and is defined as:
public AttributeList getAttributes(String[] attributeNames) { // . . . }
The
attributeNames
parameter is a String
array that contains the
names of the attributes whose values are to be retrieved. It is very
straightforward to implement this method by delegating to the
getAttribute( ) method for each attribute in the
array:
public class Queue extends Basic implements DynamicMBean { // . . . public AttributeList getAttributes(String[] attributeNames) { AttributeList resultList = new AttributeList( ); for (int aa = 0; aa < attributeNames.length; aa++){ try { Object value = getAttribute(attributeNames[aa]); resultList.add(new Attribute(attributeNames[aa], value)); } catch (Exception e) { // handle the exception. . . } } return(resultList); } // . . . }
After delegating to getAttribute( ), a new
Attribute
object is instantiated and initialized
with the name of the attribute and its value. This is the
AttributeList
object that will be returned to the
caller.
This method is used to set the values of a group of attributes and is defined as:
public AttributeList setAttributes(AttributeList attributes) { // . . . }
The attributes
parameter is an AttributeList
object. As we
discussed earlier, the AttributeList
object
contains a List
of Attribute
objects, each of which corresponds to an attribute to be set. The
following example shows how to access each
Attribute
object from the
AttributeList
and set its value by delegating to
setAttribute( ):
public class Queue extends Basic implements DynamicMBean { // . . . public AttributeList setAttributes(AttributeList attributes) { AttributeList attributeList = new AttributeList( ); for (int aa = 0; aa < attributes.size( ); aa++) { try { Attribute attribute = (Attribute)attributes.get(aa); setAttribute(attribute); String name = attribute.getName( ); Object value = getAttribute(name); attributeList.add(new Attribute(name, value); } catch (Exception e) { e.printStackTrace( ); } } return attributeList; } // . . . }
Notice the emphasized lines. We retrieve the
Attribute
object from the
AttributeList
and delegate the actual setting of
the value to setAttribute( ). The JMX
specification states that the attributes whose values were
successfully set should be returned in an
AttributeList
. It is conceivable that this list
could differ from the AttributeList
that was
passed into setAttributes( ), so we must handle
any exceptions thrown from setAttribute( )
gracefully and continue to process the remaining
Attribute
objects.
Because the attribute that has been set may not be readable, we
delegate the retrieval of its new value to getAttribute(
). If the attribute is not readable,
getAttribute( ) throws an exception that
prevents the returned AttributeList
object from
containing the Attribute
object representing that
attribute. If the attribute is readable, getAttribute(
) simply returns the value and everything proceeds.
This method is used to invoke an operation on an MBean’s management interface and is defined as:
public Object invoke(String operationName, Object params[], String signature[]) throws MBeanException, ReflectionException { // . . . }
operationName
is the name of the management operation to invoke and must match
exactly one of the operations for which there is a corresponding
MBeanOperationInfo
instance.
params is an array of objects that contains the
actual parameter values that must be passed to the operation.
signature is an array of
Class
objects that represents the signature of the
operation.
We first check to see if the operationName
parameter matches one of the operations exposed on the
MBean’s management interface. If no match is found,
a
ReflectionException
should be thrown. Otherwise, this method should check to make sure
the signature matches the expected signature of the operation. If it
does not, a ReflectionException
should be thrown.
Next, the params array should be checked to be
sure that the objects passed are actually of the correct type. If
they are not, a ReflectionException
should be
thrown. If they are of the correct type, the invocation should be
made and the parameters passed. The following example shows how to
implement this algorithm for invoke( ):
public class Queue extends Basic implements DynamicMBean { // . . . public Object invoke(String operationName, Object params[], String signature[]) throws MBeanException, ReflectionException { Object ret = null; if (operationName.equals("reset")) { reset( ); } else if (operationName.equals("suspend")) { suspend( ); } else if (operationName.equals("resume")) { resume( ); } else { throw new ReflectionException( new NoSuchMethodException(operationName), "Cannot find the operation '" + operationName + " in " + dClassName); } } // . . . }
The logic inside this method is relatively simple. We just look at
the operationName parameter, and if it matches
one of the operations for which we created an
MBeanOperationInfo
instance, we invoke the
appropriate method. Otherwise, we throw a
NoSuchMethodException
wrapped inside a ReflectionException
.