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.
- [download id=”9″]
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.