Monday, August 11, 2008

Playing with JMX 2.0 Annotations

Eamonn McManus's blog entry Playing with JMX 2.0 API provided me with the encouragement and advice I needed to try out JMX 2.0's likely annotation implementation. Toward the end of his blog entry, Eamonn provides detailed instructions on how Java SE 7 can be downloaded and the jmx.jar built and run. I followed those instructions and everything worked fine for me on my Vista-based laptop.

As explained in Playing with JMX 2.0 API (see that entry for additional/different details), the following steps can be followed to start experimenting with the current in-work version of JMX 2.0:

1. Download the JAR with the JDK source (jdk-7-ea-src-b32-jrl-04_aug_2008.jar in my case) from the Java SE 7 download site (http://download.java.net/jdk7/).

2. Execute the downloaded JAR with a command like this: java -jar jdk-7-ea-src-b32-jrl-04_aug_2008.jar and follow the wizard (including accepting the JRL license terms). Make note of where you have the source code placed. For my examples, I had the contents placed in C:\java7.

3. Change directory to the "jdk" directory under the expanded Java SE 7 source code and run Ant like this: ant -f make/netbeans/jmx/build.xml

(The -f option in Ant specifies the location of an alternative build file instead of Ant looking for a build.xml file in the same directory. Alternatively, one could change directories all the way to the make/netbeans/jmx directory and simply run "ant" there.)

4. If all has gone well, there will be a generated jmx.jar file in the dist/lib directory. The next screen snapshot shows how this appears on my machine.



I copied this generated jmx.jar file into my C:\jmx2 directory for easy reference.


5. Write the JMX 2.0 annotation-based Standard MBean and MBean Server management code.

With jmx.jar built, it can be placed on the classpath to build and run examples based on JMX 2.0. In my case, I added it to NetBeans 6.1. In a later step, I'll show how I ran the sample using it in the runtime classpath. For now, though, I will show the two code listings for my example. The first is a class called SimpleCalculator adapted from my blog entries on Model MBeans (direct, with Spring, with Apache Commons Modeler, and with EasyMBean).

SimpleCalculator.java


package dustin.jmx2;

import javax.management.Description;
import javax.management.DescriptorFields;
import javax.management.MBean;
import javax.management.ManagedOperation;

import static javax.management.Impact.INFO;

/**
* Simple calculator class intended to demonstrate use of the JMX 2.0
* annotations as currently implemented for proposed JSR-255 and Java SE 7.
*
* @author Dustin
*/
@MBean
@Description(
"Example of using JMX 2.0/JSR-255 JMX annotations to expose "
+ "an otherwise normal class as a Standard MBean.")
public class SimpleCalculator
{
/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Addition: First parameter is the augend and second parameter "
+ "is the addend.")
@DescriptorFields({"p1=augend","p2=addend"})
public int add(final int augend, final int addend)
{
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Subtraction: First parameter is minuend and second parameter "
+ "is subtrahend.")
@DescriptorFields({"p1=minuend","p2=subtrahend"})
public int subtract(final int minuend, final int subtrahend)
{
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Multiplication: First parameter is one factor and second "
+ "parameter is other factor.")
@DescriptorFields({"p1=factor1","p2=factor2"})
public int multiply(final int factor1, final int factor2)
{
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Division: First parameter is the dividend and second "
+ "parameter is divisor.")
@DescriptorFields({"p1=dividend","p2=divisor"})
public double divide(final int dividend, final int divisor)
{
return dividend / divisor;
}
}


This version of SimpleCalculator differs from the various versions used in my previous Model MBeans examples because this version uses JMX 2.0 annotations. This example is different as well in the sense that these JMX 2.0 annotations allow this class to be used as a Standard MBean rather than as a Model MBean (the Spring and EasyMBean annotations exposed their decorated SimpleCalculator class as ModelMBeans).

The four import statements in SimpleCalculator correspond to the four JMX 2.0 annotations used in the creation of the Standard MBean. The @MBean annotation on the class itself states that it will be a Standard MBean (and @MXBean does the same for MXBeans). The @ManagedOperation annotation specifies which operations are exposed by the class-turned-Standard MBean. Because I cannot specify information on the parameters to the operations like I can do with Model MBeans, I have included parameter information via the @DescriptorFields annotation. Finally, I used @Description annotations to describe the class and each of the operations. Information on each of these annotations is available in the Javadoc documentation for the javax.management package.

The next class, Main, registers the annotated SpringCalculator Standard MBean with the platform MBean server.

Main.java


package dustin.jmx2;

import java.lang.management.ManagementFactory;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

/**
* Giving JMX 2.0 (JSR-255) a spin.
*
* @author Dustin
*/
public class Main
{
/**
* Pause for the specified number of milliseconds.
*
* @param millisecondsToPause Milliseconds to pause execution.
*/
public static void pause(final int millisecondsToPause)
{
try
{
Thread.sleep(millisecondsToPause);
}
catch (InterruptedException threadAwakened)
{
System.err.println("Don't wake me up!\n" + threadAwakened.getMessage());
}
}

/**
* Main executable to test out JMX 2.0 annotation-powered MBean.
*
* @param arguments the command line arguments
*/
public static void main(final String[] arguments)
{
final SimpleCalculator calculator = new SimpleCalculator();
final String mbeanName = "jmx2:type=annotatedMBean";
try
{
final ObjectName objectName = new ObjectName(mbeanName);
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(calculator, objectName);
}
catch (MBeanRegistrationException cannotRegisterMBean)
{
System.err.println(
"ERROR: Could not register MBean with name " + mbeanName + ":\n"
+ cannotRegisterMBean.getMessage() );
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println(
"ERROR: Had trouble with ObjectName based on string "
+ mbeanName + ":\n" + badObjectName.getMessage() );
}
catch (InstanceAlreadyExistsException redundantMBean)
{
System.err.println(
"ERROR: MBean with name " + mbeanName + " already exists:\n"
+ redundantMBean.getMessage() );
}
catch (NotCompliantMBeanException notCompliantMBean)
{
System.err.println(
"ERROR: Object attempted to be used as an MBean ("
+ calculator.getClass().getCanonicalName()
+ ") is not a compliant MBean:\n" + notCompliantMBean.getMessage());
}
System.out.println("JMX 2/JSR-255 Annotation-Based MBean Registered...");
pause(100000);
}
}



6. Run the Code and Monitor with JConsole

With the above two classes implemented, they can be built and the resulting .class or .jar files can be executed. In my case, I built the classes with NetBeans 6.1 and it generated a JAR file called JMX2.jar containing the compiled Main.class and SimpleCalculator.class files.

To run the example and use the Java SE 7 jmx.jar in my Java SE 6 environment, I took advantage of Eamonn's recommendation regarding the use of the non-standard option bootclasspath to prepend (the p in the command) the JMX 2.0/Java SE 7 jmx.jar to the front of my boot class path. Running this application with this JMX 2.0/Java SE 7 jmx.jar in the boot class path looks like this:


java -Xbootclasspath/p:C:\jmx2\jmx.jar -cp JMX2.jar dustin.jmx2.Main


I can then run JConsole and view the exposed Standard MBean. The next two screen snapshots show this. The first screen snapshot shows some general MBean information and the second screen snapshot shows a particular operation being invoked via JConsole. Of special interest are the descriptor and description sections. I took advantage of both of these (the descriptors and the descriptions) to specify information about the operations' parameters that would be labeled simply as "p1" and "p2" otherwise.





I have attempted to demonstrate in this blog entry how easy it is to turn an otherwise normal Java class into a JMX Standard MBean using the proposed JMX 2.0/JSR-255 annotations. MXBeans are similarly annotated (the primary difference being the use of @MXBean rather than @MBean). The obvious advantage of these JMX 2.0 annotations is convenience and ease of use. However, Eammon points out in the "Pros and Cons of @MBeans" section of Defining MBeans with Annotations (and the Javadoc documentation points out in Defining Standard MBeans with annotations section of
javax.management documentation) that there are disadvantages to using these annotations to define standard and MXBeans. The primary disadvantages result from the mixing of potentially JMX-exposed methods in a class with non-JMX-exposed methods in the same class (and hence confusing Javadoc documentation) and from the lack of an interface to use with JMX client proxies.

2 comments:

Unknown said...

Looks a lot like Spring's Annotations for JMX.

http://static.springframework.org/spring/docs/2.5.x/reference/jmx.html#jmx-interface-annotations

@DustinMarx said...

Bhaskar,

Thanks for pointing out the similarities between the JMX 2.0 proposed annotation names and the Spring JMX annotations. In Defining MBeans with Annotations, Eamonn explained this similarity: "So our [JSR 255 Working Group] starting point was Spring's MBean annotations." The one major difference, though, is that Spring employs ModelMBeans and the JMX 2.0 annotations are targeted at the static type MBeans (Standard and MXBean) rather than at any of the dynamic type MBeans (Dynamic, Model, or Open).