Spring and JMX Integration

SHARE & COMMENT :

Introduction

In this article we will see how to integrate JMX with Spring. This article assumes that the reader has a basic understanding on Spring and JMX. We will initially explore a sample on JMX written without the support of Spring, then will learn the dis-advantages in using so. Later we will see how to use Spring’s features and support related to JMX with and without annotations. We will dedicate the final section of the article in looking into the various remoting options available in Spring for JMX clients. If you are not familiar with spring framework, please read introduction to spring.

JMX without Spring

In this section, we will see how to use JMX API without the support of Spring. After exploring this sample, readers will be able to understand the pain involved in writing a simple monitoring system that involves lot of boiler-plate code. Here comes a simple logger service which will store the log string to be logged. The string to be logged will be stored as an attribute. There is an operation for logging the log string called log().

Logger Service

LoggerService.java

package net.javabeat.spring.articles.jmx.log;

public class LoggerService {

	private String logString;

	public String getLogString() {
		return logString;
	}

	public void setLogString(String logString) {
		this.logString = logString;
	}

	public void log(){
		System.out.println(logString);
	}
}

JdkMainLog

The JdkMainLog does a lot many things. As part of the initialization process, it creates an instance of logger service that will be monitored by the MBean server. Then, for encapsulating the object name we give a description name to logger service by using ObjectName class. Next our logger service is wrapped into an appropriate model bean through RequiredModelMBean. Note that the logger service object is wrapped when a call is made to setManagedBean() defined on RequiredModelMBean object. The second argument to the method provides an indication on the type of the object to the MBean Server. Here the type is set to objectReference. Other possible types could be RMIReference, EJBHandle etc.
A call to getPlatformMBeanServer() returns the implementation of MBeanServer that ships with the JDK Platform. Now, if you look into the class definition of logger service we can see that there is a single attribute called logString and two operations getLogString() and setLogString(). The attribute logString is encapsulated through ModelMBeanAttributeInfo by passing the necessary properties such as the name, description, type etc. Similarly for encapsulating the methods getLogString() and setLogString(), the class ModelMBeanOperationInfo comes into picture for encapculating them as managed operations. Finally the managed attribute and the managed operations are encapsulated as ModelMBeanInfo object which is then registered to the Platform MBean by calling registerMBean() method.
After running the program, the Platform MBean Server will be started and the MBean object will be registered. A client application such as jConsole (which will be located in JDK_HOME/bin directory) will be needed to connect to the server.
JdkLogMain.java

package net.javabeat.spring.articles.jmx.log.jdk;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import javax.management.Descriptor;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.DescriptorSupport;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import javax.management.modelmbean.RequiredModelMBean;

import net.javabeat.spring.articles.jmx.log.LoggerService;

public class JdkLogMain {

	public static void main(String[] args) throws IOException {

		LoggerService logService = new LoggerService();
		try {
			MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

			ObjectName objectName = new ObjectName("bean:name=logService");
			RequiredModelMBean mbean = new RequiredModelMBean();

			mbean.setManagedResource(logService, "objectReference");

			Descriptor logStringDescriptor = new DescriptorSupport(new String[] {"name=logString", "descriptorType=attribute",
			"getMethod=getLogString", "setMethod=setLogString" });
			ModelMBeanAttributeInfo logStringAttribute = new ModelMBeanAttributeInfo(
			"logString", "java.lang.String", "String to be logged", true, true, false, logStringDescriptor);

			ModelMBeanOperationInfo getLogStringOperation = new ModelMBeanOperationInfo(
			"Get the log string", LoggerService.class.getMethod("getLogString"));
			ModelMBeanOperationInfo setLogStringOperation = new ModelMBeanOperationInfo(
			"Set the log string", LoggerService.class.getMethod("setLogString", String.class));

			ModelMBeanInfo mbeanInfo = new ModelMBeanInfoSupport("LoggerService", "Logger Service",
			new ModelMBeanAttributeInfo[] {logStringAttribute}, null,
			new ModelMBeanOperationInfo[] {getLogStringOperation, setLogStringOperation},null);

			mbean.setModelMBeanInfo(mbeanInfo);
			mbeanServer.registerMBean(mbean, objectName);
		}catch (Exception e) {
			e.printStackTrace();
		}
		System.in.read();
	}
}

JMX with Spring

Now we will see how to monitor the same logger service through Spring. There won’t be much coding involved in exposing the Logger Service as a managed bean though there will some wiring involved between beans in a Spring configuration file. Have a look at the following configuration file.
Spring Config

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="logService" />

<bean id="exporter" lazy-init="false">
<property name="beans">
<map>
<entry key="bean:name=logService" value-ref="logService"/>
</map>
</property>
<property name="server" ref="mbeanServer"/>
</bean>

<bean id="mbeanServer">
</bean>

</beans>

The most important points to note in the above declaration is the use of MBeanExporter and the MBeanServerFactoryBean classes. Firstly, MBeanExporter class is used to export the given objects as managed JMX Beans into the MBean Server. Note that the fully qualified class is org.springframework.jmx.export.MBeanExporter. It has a property beans that specifies the list of object to be exported as beans. In our case, it is only the logger service object. Note that key is bean:name=logService which will internally be used as the object name for the managed bean instance. The next property for the MBeanExporter is the server property which denotes to which server the beans have to be exported. It happens to be an instance of org.springframework.jmx.support.MBeanServerFactoryBean which internally uses javax.management.MBeanServerFactory class for creating the MBeanServer object.

SpringLogMain

SpringLogMain.java

package net.javabeat.spring.articles.jmx.log.spring;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringLogMain {

	public static void main(String[] args) throws Exception{

		new ClassPathXmlApplicationContext("log.xml");
		System.in.read();
	}
}

The main class is very simple as it initiates the object creating process by simply loading the context containing the very spring beans.

JMX Annotations in Spring

In this section, we will see how to use annotations for marking attributes, operations as JMX managed. This way one can exercise finer control only on the attributes and the operations that needs to be exposed. We will develop a simple application that represents a process that can contain multiple resource objects. At first, we will see the model objects.

Resource

The following model class represents the Resource that contains id, name and description as attributes. It also contains a reference to the process object that this resource is currently associated with. Class level annotation @ManagedResource() is used to mark the instances of this class as managed objects.
Resource.java

package net.javabeat.spring.articles.jmx.process;

import org.springframework.jmx.export.ann wotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(value = "Resource", description = "Any Resource",
objectName = "net.javabeat.spring.articles.jmx.process.Resource")
public class Resource {

	private String id;
	private String name;
	private String description;
	private Process process;

	public Process getProcess() {
		return process;
	}

	public void setProcess(Process process) {
		this.process = process;
	}

	public Resource(){
	}

	@ManagedAttribute(description = "Returns the id of the resource")
	public String getId() {
		return id;
	}

	@ManagedAttribute(description = "Sets the id of the resource")
	public void setId(String id) {
		this.id = id;
	}

	@ManagedAttribute(description = "Returns the name of the resource")
	public String getName() {
		return name;
	}

	@ManagedAttribute(description = "Sets the name of the resource")
	public void setName(String name) {
		this.name = name;
	}

	@ManagedAttribute(description = "Returns the name of the resource")
	public String getDescription() {
		return description;
	}

	@ManagedAttribute(description = "Sets the name of the resource")
	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public boolean equals(Object object){

		if (object instanceof Resource){
			return id.equals(((Resource)object).id);
		}
		return false;
	}

	@ManagedOperation(description = "Assigns the given process to this resource")
	public void assignToProcess(Process process){
		setProcess(process);
	}
}

Attributes that needs to be exposed should be annotated with @ManagedAttribute and operations as @ManagedOperation.

Process

Have a look at the following Process class which contains id and name as properties. It also contains a reference to the list of the resources that this process currently owns.
Process.java

package net.javabeat.spring.articles.jmx.process;

import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(value = "Process", description = "Any Process",
	objectName = "net.javabeat.spring.articles.jmx.process.Process")
public class Process {

	private String id;
	private String name;
	private Set allocatedResources;

	public Process(){
		allocatedResources = new LinkedHashSet();
	}

	@ManagedAttribute(description = "Returns the id of the process")
	public String getId() {
		return id;
	}

	@ManagedAttribute(description = "Sets the id of the process")
	public void setId(String id) {
		this.id = id;
	}

	@ManagedAttribute(description = "Returns the name of the process")
	public String getName(){
		return name;
	}

	@ManagedAttribute(description = "Sets the name of the process")
	public void setName(String name) {
		this.name = name;
	}

	@ManagedAttribute(description = "Returns the allocated resources for the process")
	public Set getAllocatedResources() {
		return allocatedResources;
	}

	@ManagedAttribute(description = "Sets the allocated resources for the process")
	public void setAllocatedResources(Set allocatedResources) {
		this.allocatedResources = allocatedResources;
	}

	@Override
	public boolean equals(Object object){

		if (object instanceof Process){
			return id.equals(((Process)object).id);
		}
		return false;
	}

	@ManagedOperation(description = "Allocates the given resource to this process")
	public void allocateResource(Resource resource){

		if (allocatedResources.contains(resource)){
			System.out.println("Resource for the process " + name + "." + id + " already exists");
		}else{
			resource.assignToProcess(this);
			allocatedResources.add(resource);
		}
	}
}

Note that this class is also annotated with @ManagedResource, @ManagedAttribute, @ManagedOperation as similarly done for Resource.

Main

In the Main program, we create instances of file resource and printer resource and assign it to some test process object. Note that we haven’t yet configured the list of objects to be available to the MBean server, which we will be doing through the configuration file.
Main.java

package net.javabeat.spring.articles.jmx.process;

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jmx.export.MBeanExporter;

public class Main {

	public static void main(String[] args) {

		Resource fileResource = new Resource();
		fileResource.setId("R-1");
		fileResource.setName("File Resource ");
		fileResource.setDescription("A sample file resource");

		Resource printerResource = new Resource();
		printerResource.setId("R-2");
		printerResource.setName("Printer resource");
		printerResource.setDescription("A sample printer resource");

		Process testProcess = new Process();
		testProcess.setId("P-1");
		testProcess.setName("Sample Process");

		testProcess.allocateResource(fileResource);
		testProcess.allocateResource(printerResource);

		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("process-resource.xml");

		System.in.read();
	}
}

Configuration file

In the below configuration, everything remains the same other the properties passed to the MBeanExporter which contains references to assembler and namingStrategy objects.
Spring Configuration file

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="exporter">
<property name="assembler" ref="assembler"/>
<property name="namingStrategy" ref="namingStrategy"/>
<property name="autodetect" value="true"/>
<property name="server" ref="mbeanServer"/>
</bean>

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

<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>

<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>

<bean id="mbeanServer">
</bean>

</beans>

Assembler objects denote the process of assembling the various meta-data information such as the attributes, operations etc. As discussed in the second section, by default Spring will include all the attributes and the public operations to get exposed which may be correct in most of the cases. In such case, assembler implementation can determine what piece of meta-data information should go while managing the resource. Spring already comes with a bunch of assemblers that assemble interfaces, method names, reflection etc. The recent addition is MetadataMBeanInfoAssembler which extracts the management information based on metadata – i.e annotation. While using this assembler, one has to specify the property attributeSource property which should be AnnotationJmxAttributeSource for reading JDK 5.0 annotations.

Remotely accessing JMX Beans

Spring provides two ways for accessing JMX beans from remote clients. One is through MBean Server connection and the second is through Proxy for the remote MBean.

Using MBean Server connection

A remote JMX client can use MBean Server Connection bean for creating a connection to the remote server. Once the connection is established, it can query for the available MBeans, then can access its properties and operations.

Test Service

TestService.java

package net.javabeat.spring.articles.jmx.remote;

public interface TestService {

	public void test();
}

For illustration purposes, we have created the above interface and below is a concrete implementation of the interface.

Test Service Implementation

TestServiceImpl.java

package net.javabeat.spring.articles.jmx.remote;

public class TestServiceImpl implements TestService{

	@Override
	public void test() {
		System.out.println("Method test invoked");
	}

}

Configuration File

Spring Configuration file

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="testService" />

<bean id="rmiRegistry" />

<bean id="mbeanServerConnection">
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/test" />
</bean>

</beans>

In the above configuration file, we have declared an instance of testService. Then a factory bean is registered for creating RMI proxies. An instance of mBeanServer Connection object has to be declared with the ‘serviceUrl’ property pointing to a valid URL.

Test Client

The Application client code is given blow. It loads the spring context, then acquires an instance to MBean Server Connection Factory bean for acquiring the connection to the remote server. For invoking the test() method on the TestService class, it creates the objectName and passes it to the invoke() operation.

TestClient.java

package net.javabeat.spring.articles.jmx.remote;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;

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

public class TestClient {

	public static void main(String[] args) throws Exception {

		ApplicationContext context = new ClassPathXmlApplicationContext("test-remote.xml");

		MBeanServerConnection connection = (MBeanServerConnection) context.getBean("mbeanServerConnection");
		ObjectName objectName = new ObjectName("bean:name=test,type=TestServiceImpl");
		connection.invoke(objectName, "test", new Object[] {}, new String[] {});
	}
}

Using Proxies

The alternate way for accessing the MBean is through Proxy which is illustrated below.

Spring Configuration file

Configuration file

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="mbeanServerConnection">
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/test" />
</bean>

<bean id="testProxy">
<property name="server" ref="mbeanServerConnection" />
<property name="objectName" value="bean:name=test,type=TestServiceImpl" />
<property name="proxyInterface" value="net.javabeat.spring.articles.jmx.remote.TestService" />
</bean>

</beans>

An instance of mBeanServer Connection is declared which is then used as reference in the MBeanProxyFactoryBean via the ‘server’ attribute. Other mandatory properties for this proxy bean are ‘objectName’ and ‘proxyInterface’ which when given will be used by the framework for getting a proxy reference to the original MBean object.

Client Application

The client code almost does the same job as the client code in the MBean Server Factory Bean. It loads the context to get a reference to the Proxy bean. Remember that the reference is a proxy reference only, actual loading of the object will take place only when the first method invocation occurs.

TestServiceProxyClient.java

package net.javabeat.spring.articles.jmx.proxy;

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

import net.javabeat.spring.articles.jmx.remote.TestService;

public class TestServiceProxyClient {

	public static void main(String[] args) {

		ApplicationContext context = new ClassPathXmlApplicationContext("test-proxy.xml");

		TestService testService =(TestService)context.getBean("testProxy");
		testService.test();
	}
}

Conclusion

JMX comes a good solution when it comes to monitoring the systems or objects. These objects are represented as MBeans and are maintained by the MBean Server. In this article, we have seen the details of integrating JMX with Spring with plenty of samples and hope the reader will know how to seamlessly integrate JMX with Spring in their applications.
If you have any questions on the spring and jmx integration, please post it in the comments section. Also search in our website to find lot of other interesting articles related to the spring framework. There are some interesting articles about spring framework, interview questions, spring and hibernate integration,etc. If you are looking for the detailed knowledge, buy any of the following books for the spring framework. Also refer the spring recommendations for spring books.

also read:

If you would like to receive the future java articles from our website, please subscribe here.

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Trackbacks

  1. […] Spring and JMX Integration […]

Speak Your Mind

*

Close
Please support the site
By clicking any of these buttons you help our site to get better