The
Spring Framework has become the most popular open source application framework in the Java space in recent years. One of its most powerful features is its ability to allow developers to create Spring Beans, which can be configured together to create an application's object graph, or data structure. The framework provides a consistent interface to accessing these beans which avoids unnecessary dependencies between components.
JMX (Java Management Extensions) is a technology that enables the configuration, instrumentation, and monitoring of Java applications of all sorts, both J2SE and J2EE. It has gained wide-spread acceptance in the Java community as the standard API for these tasks. JMX provides access to MBeans (JMX's version of
Spring's Beans), which compose its management interface. This is handled through a software layer known as the MBeanServer.
One useful piece of the Spring Framework is its direct support for JMX. Spring provides the ability to register its Beans in an MBeanServer, which can then be interrogated and modified by management applications by Spring Bean interface methods. The interface to Spring Beans is more intuitive and easier to use than the MBean interface, and abstracts many complicated details.
The follow example shows how Spring JMX can be used to provide remote management capability of its Beans. It will illustrate both server and client configuration and code. It will assume some Spring and JMX knowledge.
First, I create the Spring server-side application context configuration:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="registry" class=
"org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1090"/>
</bean>
<bean id="exporter"
class="org.springframework.jmx.export.MBeanExporter"
lazy-init="false"
<property name="beans">
<map>
<entry key="spring:name=jmxBean">
<ref local="jmxBean"/>
</entry>
</map>
</property>
<property name="assembler" ref="assembler" />
<property name="server">
<ref local="mbeanServer"/>
</property>
</bean>
<bean id="mbeanServer"
class="org.springframework.jmx.support.
MBeanServerFactoryBean"/>
<bean id="jmxBean" class="com.phurnace.JmxBeanImpl">
<property name="name">
<value>Daffy Duck</value>
</property>
<property name="rating">
<value>5</value>
</property>
</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="serverConnector"
class="org.springframework.jmx.support.
ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=rmi"/>
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:
1090/myconnector"/>
</bean>
</beans>
The MBeanExporter class is used to specify the Spring Beans that are to be exposed via JMX by the MBeanServer. The MBeanServer is implemented by the Spring MBeanServerFactoryBean class. In this example, it will expose the specifed Beans via RMI. If there is an existing MBeanServer, such as in a web container like Tomcat or JBoss, Spring JMX will use that one. Spring also allows for the specification of an RMI registry for the MBeanServer. It will be started when the application context file is loaded.
The AnnotationJmxAttributeSource and MetadataMBeanInfoAssembler classes are used allow Java 5 Annotations to indicate which classes and methods which will be exposed in the MBeanServer.
The final bean in the configuration file configures a ConnectorServerFactoryBean to provide remote access to the Spring Beans. This connector will be running over RMI, on port 1090.
Now, I configure the Spring Bean (jmxBean), which looks a whole lot like a standard Java Bean:
//
// Interface class
//
package com.phurnace;
public interface JmxBean {
public String getName();
public void setName(String name);
public int getRating();
public void setRating(int rating);
}
//
// Implementation of JmxBean interface
//
package com.phurnace;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
@ManagedResource(objectName=
"spring:name=jmxBean", description="Managed Bean")
public class JmxBeanImpl implements JmxBean {
private String name;
private int rating;
@ManagedAttribute(defaultValue="animal")
public String getName() {
return name;
}
@ManagedAttribute
(description="Name Attribute",
defaultValue="animal")
public void setName(String name) {
this.name = name;
System.out.println("Setting name to " + this.name);
}
@ManagedAttribute
(description="Rating Attribute")
public int getRating() {
return rating;
}
public void setRating(int rating) {
this.rating = rating;
}
}
As I indicated before, Spring allows usage of Java 5 Annotations to specify which classes and methods will be loaded into the MBeanServer, as well as metadata about them. Annotations are straight-forward to use, and provide great visibility to the developer as to which methods are exposed.
Finally, here is an example server, which loads up the Spring application context file.
package com.phurnace;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class SpringJmxServer {
private static Logger logger =
Logger.getLogger(SpringJmxServer.class);
/**
* @param args
*/
public static void main(String[] args) {
org.apache.log4j.BasicConfigurator.configure();
ApplicationContext ctx = new
ClassPathXmlApplicationContext("/com/phurnace/server-jsr160.xml");
if (ctx != null) {
logger.info
("Successfully loaded ApplicationContext");
}
JmxBean jmxBean = (JmxBean) ctx.getBean("jmxBean");
String name = jmxBean.getName();
logger.info("Name is " + name);
while ( !name.toUpperCase().equals("EXIT") ) {
try {
Thread.sleep(5000);
name = jmxBean.getName();
}
catch (Exception e) {
logger.error("Trapped error " +
e.getMessage());
System.exit(1);
}
}
System.exit(0);
}
}
Now, on to the client-side of things. First, I will show how to access the Spring Bean as a standard JMX client:
package com.phurnace;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.apache.log4j.Logger;
public class TestJmxClient {
private static Logger logger = Logger.getLogger(
TestJmxClient.class);
private final String objectNameStr = "spring:name=jmxBean";
private final String jmxRmiStr =
"service:jmx:rmi://localhost/jndi/rmi://localhost:1090/
myconnector";
private ObjectName objectName;
private JMXServiceURL jmxUrl;
private JMXConnector jmxConnector;
private MBeanServerConnection mbsc;
/**
* @param args
*/
public static void main(String[] args) {
org.apache.log4j.BasicConfigurator.configure();
if (args.length == 0) {
logger.info("Usage: java com.phurnace.
TestJmxClient ");
System.exit(0);
}
TestJmxClient jmxClient = new TestJmxClient();
jmxClient.init();
jmxClient.accessBean(args[0]);
System.exit(0);
}
public void init() {
try {
objectName = new ObjectName(objectNameStr);
jmxUrl = new JMXServiceURL(jmxRmiStr);
jmxConnector = JMXConnectorFactory.connect
(jmxUrl);
mbsc = jmxConnector.getMBeanServerConnection();
}
catch (Exception e) {
logger.error("Error initializing
connection");
}
}
public void accessBean(String newName) {
String name = getName();
if (name == null) {
return;
}
logger.info("Original name is " + name);
logger.info("Setting new name to " + newName);
setName(newName);
//
// Access the name again to check it's value
//
name = getName();
if (name == null) {
return;
}
logger.info("New name is " + name);
}
private void setName(final String name) {
final String operationName = "setName";
try
{
Object[] params = new Object[1];
params[0] = name;
String[] signature = new String[1];
signature[0] = new String("java.lang.String");
mbsc.invoke(objectName,
operationName,
params, // no parameter
signature );
}
catch (Exception e) {
logger.info( "Error setting name value in bean "
+ e.getMessage());
}
}
private String getName() {
final String operationName = "getName";
try {
final String status =
(String) mbsc.invoke(objectName,
operationName,
null, // no parameter
null );
return status;
}
catch (Exception e) {
logger.info("Error getting name value from
bean " + e.getMessage());
}
return null;
}
}
This example does not need any Spring classes to access the MBean, even though it is created on the server as a Spring Bean. The test client accesses the MBeanServer via the RMI protocol.
You may notice that in order to make a connection to the MBeanServer, several intermediate objects must be created, such as a JMXServiceURL, a JMXConnector, and a MBeanServerConnection. In the process of creating these objects, checked exceptions must be handled.
This leads me to my final piece of code, which illustrates the same functionality, but is implemented using Spring.
First, here is a client-side Spring configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="clientConnector" class=
"org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value=
"service:jmx:rmi://localhost/jndi/rmi://localhost:1090/myconnector"/>
</bean>
<bean id="proxy" class=
"org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="spring:name=jmxBean"/>
<property name="proxyInterface" value="com.phurnace.JmxBean"/>
<property name="server" ref="clientConnector"/>
</bean>
</beans>
This configuration file configures an MBeanServerConnectionFactoryBean to provide the client with
a connection to the server, via the RMI protocol. Then, an MBeanProxyFactoryBean is configured
to create a proxy for the MBean registered under the specified ObjectName. Finally, the client
connection bean is referenced, to allow remote access.
Finally, here is the the Spring client:
package com.phurnace;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class TestSpringJmxClient {
private static Logger logger = Logger.getLogger
(TestSpringJmxClient.class);
private ApplicationContext ctx;
private JmxBean jmxBean = null;
/**
* @param args
*/
public static void main(String[] args) {
org.apache.log4j.BasicConfigurator.configure();
if (args.length == 0) {
logger.info("Usage: java com.phurnace.
TestSpringJmxClient ");
System.exit(0);
}
TestSpringJmxClient jmxClient = new TestSpringJmxClient();
jmxClient.init();
jmxClient.accessBean(args[0]);
System.exit(0);
}
public void init() {
ctx = new ClassPathXmlApplicationContext
("/com/phurnace/client-jsr160.xml");
if (ctx != null) {
logger.info("Successfully loaded
ApplicationContext");
}
jmxBean = (JmxBean)ctx.getBean("proxy");
}
public void accessBean(String newName) {
String name = getName();
if (name == null) {
return;
}
logger.info("Original name is " + name);
setName(newName);
name = getName();
if (name == null) {
return;
}
logger.info("New name is " + name);
logger.info("Rating is " + jmxBean.getRating());
try {
jmxBean.setRating(10);
}
catch (Exception e) {
logger.warn("Unable to set rating -
attribute is read-only");
}
logger.info("Rating is still " +
jmxBean.getRating());
}
private String getName() {
return jmxBean.getName();
}
private void setName(String name) {
jmxBean.setName(name);
}
}
You might notice that the code is much more concise and clean. Except for the loading of the application context file, and the Bean access, there are no references to Spring code needed.
A try-catch block wraps the call JmxBean.setRating(10), as this method was specifically not exported by the server-side. This essentially makes this property read-only.
Hopefully, this code might encourage you to investigate Spring JMX as a technology worth incorporating in your next application. Providing external access to applications can pay off in valuable, unforseen ways after the it has been deployed.
In Untagged
Comment (0)
Read More...