Saturday, July 26, 2008

JMX Model MBeans with Spring Framework

In the creatively named blog entry The JMX Model MBean, I demonstrated with a simple example how the JMX Model MBean enables greater descriptive ability than is available with other JMX MBean types. The simple example also demonstrated that a Model MBean can be applied to an existing class with no knowledge of JMX and without any awareness that it is being exposed via a JMX management and monitoring interface. The one major drawback of Model MBeans that even this simple example demonstrated is the tedious and verbose code needed to build a Model MBean without aid of other tools or frameworks.

In that blog entry, I called out some approaches that can aid in the development of Model MBeans. These include Spring Framework’s JMX support, The Apache Commons Modeler approach, JBoss’s XMBeans, and the use of reflection. In this current blog entry, I will delve into Spring Framework’s support for Model MBeans in greater detail and demonstrate how easy Spring makes it to apply JMX Model MBeans.

By default, Spring’s support for JMX relies upon Model MBeans (though the developer has no need to deal with Model MBeans directly). The reason for this seems clear – the Model MBean allows normal Java classes with no JMX knowledge or awareness to be exposed via a JMX interface. This approach fits Spring’s behavior in other areas. In other words, it seems to be "the Spring way" to use plain Java classes with little or no knowledge of their surroundings and then "wire" them up to their surroundings via external configuration. In this blog entry, I’ll demonstrate how Spring enables Java classes with absolutely no Spring or JMX awareness to be exposed via a JMX interface and how just a little tinkering with the plain Java class via annotations enables the full descriptive power of Model MBeans to be deployed.

Although the JMX Specification (Chapter 4) talks about other potentially useful features of the Model MBean (such as persistence described in section 4.3.4), the two options of the ModelMBean that I most often take advantage of are the Model MBean’s descriptive support and the ability of the Model MBean to expose a non-JMX class in a JMX agent. In Spring, it is almost trivial to obtain this first benefit and expose a class with no awareness of JMX (or even of Spring) as a manageable and monitorable resource. In fact, the "code" needed to do this lies completely in Spring configuration. The following Java code listing contains the class (SimpleCalculator) that has no JMX awareness, but will be exposed via Spring as JMX-accessible. The XML code listing following SimpleCalculator.java shows how Spring is instructed to expose SimpleCalculator as a JMX-accessible resource.

SimpleCalculator.java


package dustin.jmx.modelmbeans;

/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be "wrapped" with ModelMBeans.
*
* @author Dustin
*/
public class SimpleCalculator implements SimpleCalculatorIf
{
/**
* 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.
*/
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.
*/
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.
*/
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.
*/
public double divide(final int dividend, final int divisor)
{
return dividend / divisor;
}
}


spring-mbean-simple-context.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

<bean id="exposedModelMBean"
class="dustin.jmx.modelmbeans.SimpleCalculator" />

<util:map id="exposedMBeans">
<entry key="modelmbean:type=spring-simple" value-ref="exposedModelMBean" />
</util:map>

<bean class="org.springframework.jmx.export.MBeanExporter"
p:beans-ref="exposedMBeans"
p:assembler-ref="assembler" />

<util:list id="manageableInterfaces">
<value>dustin.jmx.modelmbeans.SimpleCalculatorIf</value>
</util:list>

<bean id="assembler"
class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler"
p:managedInterfaces-ref="manageableInterfaces" />

</beans>



The SimpleCalculator class shown above is essentially the same class used in my previous blog entry on Model MBeans done without benefit of Spring. This is far less code than needed in my example of employing Model MBeans without the Spring Framework, but the one disadvantage is that the greater descriptive ability of the Model MBean is not enjoyed in this simple example. This is demonstrated in the following three screen snapshots of JConsole viewing this simple Spring-exposed Model MBean.








The Spring Framework provides a mechanism to provide greater descriptive information to our Spring-exposed Model MBeans. This is done via Spring-specific annotations. The downside of this approach is that the otherwise JMX-unaware and Spring-unaware classes now dependent on Spring-specific JMX-related annotations. However, the benefit of this is great descriptive ability with relative coding ease. The next code listing shows the adapted SimpleCalculatorAnnotated class that is the same class as SimpleCalculator, but with the Spring JMX annotations. The XML needed to configure this class to be exposed via Spring is then shown as well.

SpringCalculatorAnnotated.java


package dustin.jmx.modelmbeans;

import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;

/**
* Simple calculator class annotated with Spring JMX annotations to demonstrate
* using Spring JMX metadata approach. This class uses J2SE 5 annotations, but
* Spring also supports use of Apache Commons Attributes for metadata specification.
*
* @author Dustin
*/
@ManagedResource(
objectName="modelmbean:type=spring-annotated",
description="A simple integer calculator." )
public class SimpleCalculatorAnnotated implements SimpleCalculatorIf
{
/**
* 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(description="Integer Addition")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="augend",
description="The first parameter in the addition (augend)."),
@ManagedOperationParameter(
name="addend",
description="The second parameter in the addition (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(description="Integer Subtraction")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="minuend",
description="The first parameter in the substraction (minuend)."),
@ManagedOperationParameter(
name="subtrahend",
description="The second parameter in the subtraction (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(description="Integer Multiplication")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="factor1",
description="The first factor in the multiplication."),
@ManagedOperationParameter(
name="factor2",
description="The second factor in the multiplication.")})
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(description="Integer Division")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="dividend",
description="The dividend in the division."),
@ManagedOperationParameter(
name="divisor",
description="The divisor in the division.")})
public double divide(final int dividend, final int divisor)
{
return dividend / divisor;
}
}


spring-mbean-metadata-context.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

<bean id="exposedModelMBean"
class="dustin.jmx.modelmbeans.SimpleCalculatorAnnotated" />

<bean id="exporter"
class="org.springframework.jmx.export.MBeanExporter"
p:autodetect="true"
p:assembler-ref="assembler"
p:namingStrategy-ref="namingStrategy" />

<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy"
p:attributeSource-ref="attributeSource" />

<bean id="attributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />

<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"
p:attributeSource-ref="attributeSource" />

</beans>


The next three screen snapshots show via screen captures of JConsole how the Spring annotations have allowed the annotated class to provide greater descriptive details to the JMX client. The annotations are far less verbose and are arguably easier to write and maintain than the code I showed in the Model MBean blog entry in which I did not use Spring.







If it is overly burdensome or undesirable to add these annotations to the source class, one can "wrap" the source class with a class that has these annotations. An example of this is shown next. The SimpleCalculatorWrapper class "wraps" the SimpleCalculator class. In this approach, SimpleCalculatorWrapper must know about Spring and Spring’s JMX annotations, but the original SimpleCalculator does not need to be specific to Spring or to JMX.

SimpleCalculatorWrapper


package dustin.jmx.modelmbeans;

import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;

/**
* "Wraps" a SimpleCalculator class so that this wrapper class can be annotated
* with Spring JMX annotations rather than needing to modify the business logic
* Simple Calculator class.
*/
@ManagedResource(
objectName="modelmbean:type=spring-wrapper",
description="A simple integer calculator." )
public class SimpleCalculatorWrapper implements SimpleCalculatorIf
{
private SimpleCalculator simpleCalculator;

/**
* Setter method to set the object that I "wrap."
*
* @param calculator SimpleCalculator that I should wrap.
*/
public void setWrappedObject(final SimpleCalculator calculator)
{
this.simpleCalculator = calculator;
}

/**
* 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(description="Integer Addition")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="augend",
description="The first parameter in the addition (augend)."),
@ManagedOperationParameter(
name="addend",
description="The second parameter in the addition (addend).")})
public int add(int augend, int addend)
{
return simpleCalculator.add(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(description="Integer Subtraction")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="minuend",
description="The first parameter in the substraction (minuend)."),
@ManagedOperationParameter(
name="subtrahend",
description="The second parameter in the subtraction (subtrahend).")})
public int subtract(int minuend, int subtrahend)
{
return simpleCalculator.subtract(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(description="Integer Multiplication")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="factor1",
description="The first factor in the multiplication."),
@ManagedOperationParameter(
name="factor2",
description="The second factor in the multiplication.")})
public int multiply(int factor1, int factor2)
{
return simpleCalculator.multiply(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(description="Integer Division")
@ManagedOperationParameters({
@ManagedOperationParameter(
name="dividend",
description="The dividend in the division."),
@ManagedOperationParameter(
name="divisor",
description="The divisor in the division.")})
public double divide(int dividend, int divisor)
{
return simpleCalculator.divide(dividend, divisor);
}
}


spring-mbean-wrapper-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

<bean id="wrappedClass"
class="dustin.jmx.modelmbeans.SimpleCalculator" />

<bean id="exposedModelMBean"
class="dustin.jmx.modelmbeans.SimpleCalculatorWrapper"
p:wrappedObject-ref="wrappedClass" />

<bean id="exporter"
class="org.springframework.jmx.export.MBeanExporter"
p:autodetect="true"
p:assembler-ref="assembler"
p:namingStrategy-ref="namingStrategy" />

<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy"
p:attributeSource-ref="attributeSource" />

<bean id="attributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />

<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"
p:attributeSource-ref="attributeSource" />

</beans>


I won’t show the JConsole view of the wrapper because it appears nearly the same as what was shown for the directly annotated class. The only difference is that the original SimpleCalculator is left untouched by Spring-specific JMX annotations because the wrapper class shields these details from the SimpleCalculator class.

In the annotations examples above, I used the standard Java SE type annotations to provide the JMX Model MBean metadata. Spring also supports using Apache Commons Attributes for this. The Spring approach of specifying JMX ModelMBean metadata via annotations has proven so popular that it now sounds like JMX 2 is very likely to include similar, or at least Spring-inspired, annotation support. The obvious benefit of this would be the ability to enjoy highly descriptive Model MBeans without all the code even when not using Spring.

For completeness, I am including the main Java class that instantiated my three Spring-related examples above: SimpleCalculator without annotations, SimpleCalculator with annotations, and a "wrapped" SimpleCalculator.


package dustin.jmx.modelmbeans;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


/**
* The purpose of this class is to demonstrate use of ModelMBeans with Spring.
*/
public class SpringModelMBeanDemonstrator
{
/**
* 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());
}
}

public static void main(final String[] arguments)
{
String option;
if ( arguments.length < 1 )
{
option = "simple";
}
else
{
option = arguments[0].toLowerCase();
}

ApplicationContext context;
// assume simple example unless metadata or wrapper explicitly requested
if ( option.equalsIgnoreCase("wrapper") )
{
System.out.println("Spring Wrapper Approach");
context = new ClassPathXmlApplicationContext(
"/dustin/jmx/modelmbeans/spring-mbean-wrapper-context.xml");
}
else if ( option.equalsIgnoreCase("metadata")) // metadata example
{
System.out.println("Spring Metadata Approach");
context = new ClassPathXmlApplicationContext(
"/dustin/jmx/modelmbeans/spring-mbean-metadata-context.xml");
}
else // use simple if neither wrapper nor metadata specified
{
System.out.println("Spring Interface Approach");
context = new ClassPathXmlApplicationContext(
"/dustin/jmx/modelmbeans/spring-mbean-simple-context.xml");
}

final SimpleCalculatorIf calculator =
(SimpleCalculatorIf) context.getBean("exposedModelMBean");
pause(1000000);
}
}


I also added an interface, SimpleCalculatorIf, to these examples to make them easily accessible via the same code for any of the three approaches covered in this blog entry. That simple interface listing is shown next.

SimpleCalculatorIf.java

package dustin.jmx.modelmbeans;

/**
* Interface to expose Model MBean via Spring.
*/
public interface SimpleCalculatorIf
{
public int add(final int augend, final int addend);

public int subtract(final int minuend, final int subtrahend);

public int multiply(final int factor1, final int factor2);

public double divide(final int dividend, final int divisor);
}


Spring makes use of JMX easier in general. Because Spring uses Model MBeans anyway for its JMX support, it is easy to apply the descriptive power of the Model MBean to one’s Spring-exposed JMX MBeans using annotations.

See the blog entry Adding Information to a Standard MBean Interface Using Annotations for a method of using annotations processing to provide descriptive detail via Standard MBeans (as opposed to Model MBeans).




UPDATE (7 February 2009): The following section has been added in response to a request in the feedback section. This section demonstrates how to build and run the code examples shown earlier in the blog post.

The Java source files and XML configuration files for Spring shown above can be placed in different locations depending on how the build file is set up. I will provide an Ant build.xml file that generates a JAR file and a script for running the examples. This build file depends on the Java source code files and Spring Framework configuration files being in a directory structure as shown in the next two screen snapshots.

Placement of Java Source Code Files



Placement of Spring Framework XML Configuration Files



As the screen snapshots above demonstrate, I placed the directories and files under a root directory called C:\dustinjmx. The src and config subdirectories hold the Java source code files and Spring XML configuration files respectively. With this structure in place, the following Ant build.xml file can be used to compile the Java source code and assemble a JAR file. This build file also optionally generates a script that can be used to run the Java/Spring examples.

build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name="SpringModelMBeansExamples" default="all" basedir=".">

<property name="spring.home.dir"
value="C:\\spring-framework-2.5.6" />

<property name="config.dir" value="config" />
<property name="src.dir" value="src" />
<property name="build.dir" value="build" />
<property name="classes.dir" value="${build.dir}/classes" />
<property name="dist.dir" value="dist" />
<property name="jar.name" value="springjmx.jar" />

<property name="command.string"
value="java -cp ${spring.home.dir}/dist/spring.jar${path.separator}${spring.home.dir}/lib/jakarta-commons/commons-logging.jar${path.separator}${dist.dir}/${jar.name} dustin.jmx.modelmbeans.SpringModelMBeanDemonstrator" />
<property name="script.name" value="runIt" />

<target name="-init">
<mkdir dir="${dist.dir}" />
<mkdir dir="${classes.dir}" />
</target>

<condition property="isWindows" value="true">
<os family="windows" />
</condition>

<target name="compile"
depends="-init"
description="Compile Java classes.">
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
classpath="${spring.home.dir}/dist/spring.jar:${spring.home.dir}/lib/jakarta-commons/commons-logging.jar" />
</target>

<target name="jar" depends="compile"
description="Assemble class files and context XML files into JAR.">
<jar destfile="${dist.dir}/${jar.name}"
basedir="${classes.dir}"
includes="**/*.*"
filesonly="true">
<fileset dir="${config.dir}" includes="**/*.*" />
</jar>
</target>

<target name="clean"
description="Clean generated artifacts.">
<delete dir="${dist.dir}" />
<delete dir="${build.dir}" />
</target>

<target name="display-run-command"
description="Shows how to run these examples.">
<echo>
<![CDATA[
Run the assembled executable Spring JMX Demonstration code
with a command like the following:

${command.string} <<<demonstrator_type>>>

where <<<demonstrator_type>>> is one of the following:

simple
wrapper
metadata
]]>
</echo>
</target>

<target name="generate-run-scripts"
description="Generate script to run JMX examples"
depends="generate-windows-run-script, generate-linux-run-script" />

<target name="generate-windows-run-script"
description="Generate run script for Windows"
if="isWindows">
<echo file="runIt.cmd">${command.string} %1</echo>
</target>

<target name="generate-linux-run-script"
description="Generate run script for Linux."
unless="isWindows">
<echo file="runIt.sh">#!/bin/bash${line.separator}${command.string} $1</echo>
</target>

<target name="all" depends="jar, display-run-command"
description="Default target: Compiles Java source and assembles JAR" />

</project>



The location of the Spring Framework should be changed in the build file to point to wherever it is located on the individual machine. The generate-run-scripts target can be called with ant generate-run-scripts to build a script that can then be run to start the examples covered earlier in this blog posting. When the script is run, it should be run with a command-line option of simple, wrapper, or metadata like this: runIt simple.



Once this script has been started, JConsole can be started in a different window by typing "jconsole" on the command line. If JConsole is run on the same machine as the Java application just started, the it should be a local connection that can be selected as shown in the following screen snapshot. Use the MBeans tab to interact with the Java application as shown earlier.

3 comments:

Kamcia said...

could you please write how to run your example in jconsole?

@DustinMarx said...

Spiderka,

I have added a new section at the bottom of the blog post that shows how I structured the directories with Java and XML files, how to build these into a single JAR, and how to run the main class in that JAR. Once the Java/Spring code is running, the JMX portions can be accessed via JConsole by starting JConsole up separately and connecting to the previously started example Java application.

Take a SIMPLE idea and take it Seriously ! said...

Hi

Is it possible to restrict the input parameters that are being passed to a method that is exposed via managed operation.

For example , in your add method only 5 and 10 can be passed as first and second parameter respectively.

Thanks