Using Spring JMX within Java Applications

Exclusive offer: get 50% off this eBook here
NetBeans Platform 6.9 Developer's Guide

NetBeans Platform 6.9 Developer's Guide — Save 50%

Create professional desktop rich-client Swing applications using the world's only modular Swing application framework

$23.99    $12.00
by Eric Spiegelberg | August 2010 | Java Open Source

Java Management Extensions (JMX) is an incredibly powerful Java technology. Introduced in 2003 as part of Java 5 and the result of multiple Java Community Process specifications (JSR-3 and JSR-160), JMX defines an architecture, API, and services for the management and monitoring of Java applications. Simply put, JMX is a standard part of Java used to interact with a running application. Once started, you probably tend to think of an application as if it were on its own island and that it's difficult to communicate with. Or perhaps you've never needed to interact with a running application or didn't realize such functionality was a possibility. JMX makes these interactions possible and can be used to receive event notifications or to invoke the functionality (such as business logic) contained within your running application. These interactions occur between a JMX client running locally and any Java Virtual Machine (JVM) running on a local or remote machine.

The topic of the article by Eric Spiegelberg, is a design for using JMX and Spring to interact with applications at runtime; the writing is one part a very light introduction to JMX, one part an introduction to the benefits of Spring JMX, and one part that presents a design for how to quickly and conveniently maximize the use of Spring JMX within your Java applications.

(For more resources on Java, see here.)

Yet for all its powerful capabilities, JMX is greatly underutilized and few developers seem to take advantage of its power. I attribute this underutilization to two factors: the scope of the Java universe as well as JMX's complex development model. As a deep and wide universe composed of a seemingly infinite number of tools, frameworks, design patterns and a never ending stream of new thoughts and ideas, I believe that JMX rarely finds itself on the list of the next technologies a developer plans to explore. While other shiny objects steal the Java community spotlight, the benefits of JMX patiently wait to be discovered and seem to largely be the playing field of only seasoned Java veterans who have had the time or industry longevity to have already encountered it. In regard to its complex development model, JMX itself has an extremely low level, clumsy, and obtrusive API and that has directly hindered its adoption. While this complex development model is a fact of JMX life, the Spring framework, as with numerous other aspects of Java development, offers excellent JMX support that greatly simplifies and radically reduces the learning curve and time investment required to incorporate JXM into your application. Spring's JMX support transforms JMX from an obscure API into what could become a central component of your application's architecture.

While all of this sounds great, a tangible example of how easy it is to incorporate Spring JMX (and therefore JMX itself) into your application will make things more concrete. The following code and configuration sample presents a classic example of the benefits of JMX and is a piece of functionality which has proven its usefulness dozens upon dozens of times within my career: the ability to dynamically change an application's Log4j log level at runtime.

Example 1:

package com.spiegssoftware.common.util.management.logging;

import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.jmx.export.annotation.ManagedOperation;

/**
* MBean exposing Log4j management operations.
* <p>
* This code is based on an example provided from http://uri.jteam.nl/?p=4 .
*/
public class Log4jJmxService
{
/** Logger for this class. */
private final Logger logger = Logger.getLogger(Log4jJmxService.class);

@ManagedOperation(description = "Set this Logger to the DEBUG level")
public boolean activateDebug(final String category)
{
return adjustLogLevel(category, Level.DEBUG);
}

@ManagedOperation(description = "Set this Logger to the INFO level")
public boolean activateInfo(final String category)
{
return adjustLogLevel(category, Level.INFO);
}

@ManagedOperation(description = "Set this Logger to the WARN level")
public boolean activateWarn(final String category)
{
return adjustLogLevel(category, Level.WARN);
}

@ManagedOperation(description = "Set this Logger to the ERROR level")
public boolean activateError(final String category)
{
return adjustLogLevel(category, Level.ERROR);
}

@ManagedOperation(description = "Set this Logger to the FATAL level")
public boolean activateFatal(final String category)
{
return adjustLogLevel(category, Level.FATAL);
}

protected boolean adjustLogLevel(final String category, final Level level)
{
boolean result = false;

Category cat = LogManager.exists(category);

if (cat == null)
{
logger.error("Logger '" + category + "' does not exist");
}
else
{
logger.info("Activating " + level + " for category: " + category);
cat.setLevel(level);
result = true;
}

return result;
}
}

 

Example 2:

<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<bean id="log4jJmxService" class="com.spiegssoftware.common.util.management.logging.Log4jJmxService" />

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<util:map id="beans">
<entry key="com.spiegssoftware.common.util.management.logging:name=Log4jJmxService" value="log4jJmxService"/>
</util:map>
</property>
</bean>

</beans>

 

Based on code publicly available from Dev Thoughts, the class in Example 1 was decorated with Spring JMX annotations and the necessary Spring configuration was created in Example 2. To incorporate this functionality into your application, you will need to include this class (along with Spring JMX's dependencies) and the configuration into your project, rebuild, and deploy. Before starting your application server, you may need to enable its support for JMX; see the documentation for your specific application server for details. After your application server has started with JMX support enabled, any JMX console, such as the jConsole tool that ships with all recent JDK's, can be used to connect to the JVM the application is running within and the application's logging level can be adjusted without requiring a restart.

The details of how to use jConsole are best left to its documentation, but for the impatient, jConsole can by be launched by opening a command window and issuing a "jconsole" command just as you would issue a "java -version". From there, select which JVM you wish to connect to; most likely you will want to connect to a local process. After selecting the MBeans tab, use the left hand navigation and find the Log4jJmxService under the key name you registered it under within your Spring configuration file; in Example 2 we chose to use a value of "log4jJmxService". After selecting the Log4jJmxService from the jConsole tree navigation and drilling down, you are presented with a screen that represents all of the public methods available on the Log4jJmxService. Simply clicking the invoke button next to each available public method results in the specified method on the Log4jJmxService being invoked just as if the bean's method had been invoked through traditional application user input; the application is unaware and indifferent as to the source of the invocation request and the normal execution flow takes place.

You now have the ability to dynamically change the log level of your application at runtime. This JMX stuff is great, hu?

With your toe now in the JMX waters, you're undoubtedly thinking of the numerous ways JMX can be incorporated within your applications: to inspect or alter application configuration, to access statistical data held within an application memory, or to manage an application by invoking application logic – all at runtime! JMX's uses are limited only by your creativity to incorporate it.

JMX is so powerful and exposing your Spring based components through Spring JMX is so easy and convenient that it's likely you'll quickly find yourself wanting to expose every Spring bean throughout your entire application. While the two configuration strategies provided by Spring JMX (annotating classes or configuring beans in XML) are suitable for configuring a relatively low number of beans, when applied on a large scale each strategy has the disadvantage that it becomes tedious, verbose, and is the epitome of boilerplate; few would dispute that very quickly either your code or configuration becomes cluttered with JMX metadata. Having previously fallen into this advantageous trap of wanting to expose all Spring beans within an application multiple times before myself, it was time to take a step back and determine if this could accomplished in a better way.

NetBeans Platform 6.9 Developer's Guide Create professional desktop rich-client Swing applications using the world's only modular Swing application framework
Published: August 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

(For more resources on Java, see here.)

Let's assess the situation: we want to quickly and conveniently expose all Spring beans through Spring JMX with minimal changes or effort. The very fact that each bean is managed by Spring implies that each exists and is programmatically available from within your application's ApplicationContext. With this key realization, a design emerges in which rather than annotating or configuring numerous individual Spring beans with Spring JMX metadata we could instead create a single component which itself is exposed through Spring JMX and is then used to provide indirect programmatic access to other Spring beans. The code for the I call the ReflectiveApplicationContextExporter, which is named in line with existing conventions within Spring's JMX packages, is shown below.

Example 3:

/**
*
*/
package com.spiegssoftware.jmx;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.util.ClassUtils;

/**
* Expose the <code>ApplicationContext</code> through JMX.
*/
public class ReflectiveApplicationContextJmxExporterBean implements ApplicationContextAware
{
/** The managed <code>ApplicationContext</code>. */
private ApplicationContext applicationContext;

@ManagedOperation(description = "Get the property value from the specified bean")
@ManagedOperationParameters( {
@ManagedOperationParameter(name = "beanName", description = "The name of the bean"),
@ManagedOperationParameter(name = "propertyPath", description = "The property path of the bean you wish to get") })
public Object getBeanPropertyValue(String beanName, String propertyPath)
{
BeanWrapper beanWrapper = getBeanWrappedBeanFromApplicationContext(beanName);
Object propertyValue = beanWrapper.getPropertyValue(propertyPath);

return propertyValue;
}

@ManagedOperation(description = "Set a property value of the specified bean")
@ManagedOperationParameters( {
@ManagedOperationParameter(name = "beanName", description = "The name of the bean"),
@ManagedOperationParameter(name = "propertyPath", description = "The property path of the bean you wish to get"),
@ManagedOperationParameter(name = "propertyValue", description = "The value to assign") })
public void setBeanPropertyValue(String beanName, String propertyPath, Object propertyValue)
{
BeanWrapper beanWrapper = getBeanWrappedBeanFromApplicationContext(beanName);

beanWrapper.setPropertyValue(propertyPath, propertyValue);
}

@ManagedOperation(description = "Invoke a no argument method on the specified bean")
@ManagedOperationParameters( {
@ManagedOperationParameter(name = "beanName", description = "The name of the bean"),
@ManagedOperationParameter(name = "methodName", description = "The name of method to invoke") })
public Object invokeBeanMethod(String beanName, String methodName) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
Object result = null;
Object object = getBean(beanName);

Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, null);

if (method == null)
{
throw new IllegalArgumentException("Unable to find methodName '" + methodName + "' on " + object.getClass() + " with no argument");
}
else
{
result = method.invoke(object);
}

return result;
}

/**
* Get a bean from the <code>ApplicationContext</code> and wrap it with a
* <code>BeanWrapper</code>.
*
* @param beanName The name of the bean to get.
* @return The bean already wrapped by a <code>BeanWrapper</code>.
*/
protected BeanWrapper getBeanWrappedBeanFromApplicationContext(String beanName)
{
Object object = applicationContext.getBean(beanName);
BeanWrapper beanWrapper = new BeanWrapperImpl(object);

return beanWrapper;
}






@ManagedOperation(description = "Set a property value of the specified bean")
@ManagedOperationParameters( { @ManagedOperationParameter(name = "beanName", description = "The name of the bean to get") })
protected Object getBean(String beanName)
{
Object bean = applicationContext.getBean(beanName);

return bean;
}

/**
* Get the <code>ApplicationContext</code>.
*
* @return The <code>ApplicationContext</code>.
*/
@ManagedAttribute(description = "Get the ApplicationContext")
public ApplicationContext getApplicationContext()
{
return applicationContext;
}

/**
* Set the <code>ApplicationContext</code>.
*
* @param applicationContext The <code>ApplicationContext</code>.
*/
public void setApplicationContext(ApplicationContext applicationContext)
{
this.applicationContext = applicationContext;
}

@ManagedOperation(description = "Invoke a no argument method on the specified bean")
@ManagedOperationParameters( {
@ManagedOperationParameter(name = "beanName", description = "The name of the bean"),
@ManagedOperationParameter(name = "methodName", description = "The name of method to invoke"),
@ManagedOperationParameter(name = "methodParameter", description = "The method parameter") })
public Object invokeBeanMethod(String beanName, String methodName, Object... methodParameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
Object result = null;
Object object = getBean(beanName);

Class paramTypes[] = new Class[methodParameters.length];

for (int i = 0; i < methodParameters.length; i++)
{
paramTypes[i] = methodParameters[i].getClass();
}

Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);

if (method == null)
{
throw new IllegalArgumentException("Unable to find methodName '" + methodName + "' on " + object.getClass() + " with argument(s) of " + methodParameters);
}
else
{
result = method.invoke(object, methodParameters);
}

return result;
}
}

 

As you can see from the Example 3, by invoking the getBeanPropertyValue with proper input parameters, such as any valid Spring bean name and valid property name on that bean, the code programmatically retrieves a bean reference from the ApplicationContext. This bean reference is wrapped in the BeanWrapper implementation of Spring's PropertyAccessor interface to provide access to named properties (such as bean properties of an object or fields in an object). Through the use of the underlying BeanWrapper, reflection is used and the appropriate method(s), such as an individual or a nested chain of getter methods, is invoked and the value of the desired property is ultimately returned to the invoking method. Conceptually the exact same process takes place for the setBeanPropertyValue method with the obvious deviation in that a property value is set rather than retrieved. In the case of the invokeBeanMethod method, the bean is retrieved from the ApplicationContext by the provided name, a check is performed to determine if the method of the provided name does in fact exist, and if it exists then reflection is used to invoke it. It is interesting to note that the majority of the heavy lifting, such as the majority of the use of the reflection, is performed by out of the box Spring functionality; ultimately the code sample simply merges the idea of providing a single Spring JMX enabled component that is used to programmatically retrieve a bean and invoke methods on it through Spring's own reflection infrastructure. Using these simple but foundation methods, the ReflectiveApplicationContextExporter provides the flexibility and power to invoke any public method on any Spring bean within your entire application and bridges the gap between your application and Spring JMX by sidestepping the need to annotate or configure each individual Spring bean.

Unfortunately, this power and flexibility is not free from costs or responsibilities.

You undoubtedly noticed that reflection is used heavily within the ReflectiveApplicationContextExporter. While the debate seems to be over, at one time many within the Java community were cautious about widespread use of reflection and within some circles the use of reflection was synonymous with poor performance. While it is true that invoking a method through reflection is likely to be initially slower than invoking it directly, modern JVM's have minimized the performance penalty to a negligible amount. Additionally, given the emergence of top tier application frameworks and tools such as Spring, Hibernate, and any second generation web MVC framework that all make extensive use of reflection, reflection seems to have gone from a generally discouraged practice to a cornerstone in the foundation of modern Java software. Finally, when considering the use cases in which we are likely to employ the ReflectiveApplicationContextExporter in the legacy performance concerns of using reflection, valid or not, should be a non-factor.

Security is an obviously large responsibility of using the ReflectiveApplicationContextExporter. Because the component exposes all of your application's Spring beans through JMX, it must be ensured that only authorized users are able to gain access to this functionality. At the JMX level, the specification provides mechanisms for employing the use of encryption, user name and password authentication, and file access control; please see the JMX specification for more details and information. At the server/network level, SSL can be used to encrypt network traffic. At the application level, the ReflectiveApplicationContextExporter could be extended or proxied by an AOP interceptor to restrict access based on any sort of authentication or authorization criteria that fits your application or organizational needs.

Summary

Traditionally an obscure and underutilized API, JMX is extremely powerful and, as with many other aspects of Java development, the Spring framework greatly simplifies its use. When incorporating JMX within your application, between choosing to manually expose everything (and therefore cluttering your code or configuration) and exposing nothing, lies the solution of using the ReflectiveApplicationContextExporter. By indirectly exposing all Spring-managed beans through JMX without the need to define per-bean JMX-specific metadata, you can quickly and effortlessly take advantage of JMX's flexibility and power to interact, manage, and monitor applications in ways previously not conveniently possible.

Resources

Dev Thoughts: http://uri.jteam.nl/?p=4

http://java.sun.com/j2se/1.5.0/docs/guide/jmx/

http://java.sun.com/javase/6/docs/technotes/guides/jmx/index.html

http://java.sun.com/javase/6/docs/technotes/guides/jmx/overview/intro.html#wp5529

Spring JMX: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jmx.html


Further resources on this subject:


Spring Security 3 Secure your web applications against malicious intruders with this easy to follow practical guide
Published: May 2010
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Eric Spiegelberg is a Minneapolis based Java consultant. Graduating from the University of Minnesota with a BS in Computer Science, he also holds SJCP 1.2, 1.4, 5.0, SCWD, SCEA, and SpringSource Certified Professional certifications and is currently pursuing a Masters degree in Software Engineering. Experienced with both large scale "traditional" and AJAX applications, Eric's technical interests include projects within the Spring portfolio, Hibernate, and Tomcat. A frequent technical writer published through DZone, O'Reily, and Packt Publishing, outside of technology Eric recently ran the Paris Marathon in France, is pursuing his instrument private pilot rating, and is enjoying an interest in travel.

Books From Packt


JavaFX 1.2 Application Development Cookbook
JavaFX 1.2 Application Development Cookbook

Spring Persistence with Hibernate
Spring Persistence with Hibernate

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development

RESTful Java Web Services
RESTful Java Web Services

Java EE 6 with GlassFish 3 Application Server
Java EE 6 with GlassFish 3 Application Server

JasperReports 3.6 Development Cookbook
JasperReports 3.6 Development Cookbook

Spring 2.5 Aspect Oriented Programming
Spring 2.5 Aspect Oriented Programming

MooTools 1.2 Beginner's Guide
MooTools 1.2 Beginner's Guide


No votes yet
good example by
its working fine

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
X
s
R
V
j
r
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software