JMX and MBeans with Spring

by Prasanth Gullapalli

JMX defines an architecture for management and monitoring of long running Java applications. Simply put, it provides an interface for interacting with a running application. So through JMX, we can check/change the state of variables or to invoke a method in a (remote) running application via a management GUI such as JConsole. To make this possible, we have to define an MBean in our application. We can define MBean as a managed Java object, similar to a JavaBeans component, that follows the JMX specification which consists of readable/writeable attributes and invokable operations. It can be used for collecting statistics on concerns like performance, resources usage, or problems (pull); for getting and setting application configurations or properties (push/pull); and notifying events like faults or state changes (push).

Spring makes it very easy to expose any POJO as MBean with little configuration. In this article, I will share an example which will change the log level for the category requested.

package com.pramati.jmx.mbeans;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
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;
import org.springframework.stereotype.Component;

@Component
@ManagedResource(objectName="com.pramati.jmx.mbeans:name=LoggerConfigurator",
		description="Get/Set log level for the category specified")
public class LoggerConfigurator {

	@ManagedOperation(description = "Get the logging level for a category")
    @ManagedOperationParameters( { @ManagedOperationParameter(name = "category", description = "Logger category") })
    public String getLoggerLevel(String category) {
		Logger logger = Logger.getLogger(category);
		return "Logger level is "+((logger.getLevel() != null)?logger.getLevel().toString():" not defined");
	}

	@ManagedOperation(description = "Set the logging level for a category")
	@ManagedOperationParameters({ @ManagedOperationParameter(name = "category", description = "Logger category"),
            @ManagedOperationParameter(name = "level", description = "Logging level") })
    public void setLoggerLevel(String category, String level) {
		Logger logger = Logger.getLogger(category);
		logger.setLevel(Level.toLevel(level,Level.INFO));
	}

}

As can be seen from the code above, we defined a managed resource LoggerConfigurator which has got two managed operations: getLoggerLevel and setLoggerLevel. In addition to managed operations, we can have managed attributes as well. For example:

package com.pramati.jmx.mbeans;

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.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;

@Component
@ManagedResource(objectName="com.pramati.jmx.mbeans:name=SimpleCalculator",
		description="Calculator performing basic arithmetic on integers",
		log=true, logFile="jmx.log")
public class SimpleCalculator {

	private int operand1;
	private int operand2;

	@ManagedOperation(description="Addition operation")
	public int add() {
		return operand1 + operand2;
	}

	@ManagedOperation(description="Multiplication operation")
	public int multiply() {
		return operand1 * operand2;
	}

	@ManagedOperation(description="Division operation")
	@ManagedOperationParameters({
		@ManagedOperationParameter(name="operand1", description="Dividend"),
		@ManagedOperationParameter(name="operand2", description="Divisor")
	})
	public int divide(int operand1, int operand2) {
		if(operand2 == 0) {
			throw new IllegalArgumentException("Can not divide by zero");
		}
		return operand1 / operand2;
	}

	@ManagedAttribute
	public int getOperand1() {
		return operand1;
	}

	@ManagedAttribute
	public void setOperand1(int operand1) {
		this.operand1 = operand1;
	}

	@ManagedAttribute
	public int getOperand2() {
		return operand2;
	}

	@ManagedAttribute
	public void setOperand2(int operand2) {
		this.operand2 = operand2;
	}

}

Now in order to register these beans as MBeans into a JMX Server, we have to just provide this single line of configuration in Spring:

<context:mbean-export/>

After this when you start your application, you can see from the Spring logs that the beans defined above are registered as MBeans in JMX Server:

 [main] DEBUG org.springframework.jmx.support.JmxUtils - Found MBeanServer: com.sun.jmx.mbeanserver.JmxMBeanServer@ef137d
 [main] INFO  org.springframework.jmx.export.annotation.AnnotationMBeanExporter - Registering beans for JMX exposure on startup
 [main] DEBUG org.springframework.jmx.export.annotation.AnnotationMBeanExporter - Autodetecting user-defined JMX MBeans
 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'loggerConfigurator'
 [main] INFO  org.springframework.jmx.export.annotation.AnnotationMBeanExporter - Bean with name 'loggerConfigurator' has been autodetected for JMX exposure
 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'simpleCalculator'
 [main] INFO  org.springframework.jmx.export.annotation.AnnotationMBeanExporter - Bean with name 'simpleCalculator' has been autodetected for JMX exposure
 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'simpleCalculator'
 [main] INFO  org.springframework.jmx.export.annotation.AnnotationMBeanExporter - Located managed bean 'simpleCalculator': registering with JMX server as MBean [com.pramati.jmx.mbeans:name=SimpleCalculator]
 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'loggerConfigurator'
 [main] INFO  org.springframework.jmx.export.annotation.AnnotationMBeanExporter - Located managed bean 'loggerConfigurator': registering with JMX server as MBean [com.pramati.jmx.mbeans:name=LoggerConfigurator]
 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'mbeanExporter'

Points to note:

1. In order to access your MBean, we have to access JConsole. To enable local monitoring of our application through JConsole, we have to pass few JVM arguments when we run our program:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8855
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

If it is a web application, then copy paste the following line in catalaina.bat file:

set JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8855 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false

Do not forget to add %JAVA_OPTS%. Otherwise it will override but we just want to append to the existing options.

2. In case of web application, make sure ‹context:mbean-export/› is called after the component-scan definitions. The problem I have faced is: I defined my component-scan declarations in spring-servlet.xml(specific to dispatcher-servlet) and ‹context:mbean-export/› in applicationContext.xml which is placed under the resources folder of Maven project structure. The problem is that I can see from the application logs that jmx specific beans are getting created but none of the them are getting initialized as mbeans i.e. they are not visible in JConsole. Then after enabling spring logs I found that mbean export is happening much prior to component scan being done. And hence jmx server is not able to find any beans. Then I moved my ‹context:mbean-export/› to spring-servlet.xml after the component-scan declarations and it started working fine.

Now to the see the mbeans registered, just type jconsole in the command prompt to open jconsole. Once jconsole opens up, in the ‘Remote process’, give your (local ip:jmx remote port). Here is the screen shot:

JConsole1

Once the JConsole, we can just go to MBeans tab and see the mbeans registered.

JConsole2

Now we can change the log levels as desired for the categories by inputting them in JConsole and clicking on setLoggerLevel Button. That’s it. You can also add MBean annotations to any regular Bean that you wish to inspect at runtime. For example, you can add it to the bean that manages your thread pool to see its utilization at runtime, you can add it to any inmemory cache to find out memory leaks. The possibilities can be endless!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s