Friday, February 15, 2008

Spring and JMX: Two Assemblers

The Spring framework makes it very simple to expose normal Java objects as JMX (Java Management Extensions) MBeans via Spring JMX configuration and no changes to the actual Java class.

The following code snippet shows a normal Java class (POJO) that will be exposed as a JMX MBean via the Spring framework.

StateHandler.java
package dustin;

/**
 * Handle state information.  This normal Java class will be turned into an
 * MBean via Spring MBean exposure.
 *
 * @author Dustin
 */
public class StateHandler
{
   private String stateName;
   private String stateCapital;
   private int applicationState;

   public StateHandler()
   {
   }

   public String getState()
   {
      return this.stateName;
   }

   public void setState(final String aStateName)
   {
      this.stateName = aStateName;
   }

   public String getCapital()
   {
      return this.stateCapital;
   }

   public void setCapital(final String aStateCapital)
   {
      this.stateCapital = aStateCapital;
   }

   public void setState(final int aState)
   {
      this.applicationState = aState;
   }

Note that the Java class defined above does not implement any interfaces and is so basic that it does not require even a single import statement. One may argue that this is a contrived example, but it will illustrate some issues later in this blog entry. More specifically, I intentionally named two methods setState that are overloaded by different parameter data types. A better class design would have named these method names a lengthier, differentiating name, but I intentionally wanted these same-named methods to illustrate a point.

The main application that loads the Spring context is shown next.

JmxSpringMain.java
package dustin;

import java.io.IOException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Simple example demonstrating Spring-exposed JMX beans.
 * 
 * @author Dustin
 */
public class JmxSpringMain
{
   /**
    * Main executable.
    * 
    * @param args the command line arguments
    */
   public static void main(String[] aCommandLineArgs)
   {
      ConfigurableApplicationContext context =
         new FileSystemXmlApplicationContext("C:\\NetBeansProjects\\JmxSpringExposedMethodNames\\spring-jmx-config.xml");
      StateHandler state = (StateHandler) context.getBean("stateHandler");
      System.out.print("Press <ENTER> to continue...");
      try
      {
         final int throwAway = System.in.read();
      }
      catch ( IOException ioEx )
      {
         System.err.println("Error waiting for ENTER key to be pressed.");
      }
      context.close();
   }
}

The code listing for this class shows that it is a very simple executable class mainly meant to bootstrap a Spring container in a Java SE environment and then wait for pressing of the ENTER key. This forced suspension of execution is intentional so that the process will not complete too quickly. This allows for JConsole to be used to monitor the StateHandler class once Spring has turned it into a Model MBean.

The Spring XML configuration that is accessed by the main class and which instructs Spring to expose StateHandler as a JMX Model MBean is shown next.

spring-jmx-config.xml (without JMX MBean Exporter Assembler)
<?xml version="1.0" encoding="UTF-8"?>
<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.0.xsd">

   <bean id="stateHandler"
         class="dustin.StateHandler">
      <property name="state" value="Colorado" />
      <property name="capital" value="Denver" />
   </bean>

   <bean class="org.springframework.jmx.export.MBeanExporter">
      <property name="beans">
         <map>
            <entry key="dustin.example:name=jmx,type=spring"
                   value-ref="stateHandler" />
         </map>
      </property>
   </bean>

   <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
      <property name="port" value="1099"/>
   </bean>

   <bean id="serverConnector"
         class="org.springframework.jmx.support.ConnectorServerFactoryBean"
         depends-on="registry">
      <property name="objectName" value="connector:name=rmi"/>
      <property name="serviceUrl"                value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
   </bean>

</beans>

With the code shown in the listings above, the Spring framework will expose all public methods defined in StateHandler as JMX MBean operations and will expose attributes based on get methods defined in StateHandler. This is illustrated by the following snapshot of JConsole when attached to this Spring-powered and Spring-exposed MBean (click on images to see larger versions).

As the above screen snapshot shows, JConsole is providing access for administrators to change the state name and state capital via JMX. This is probably not intended and we really only want to change the application state rather than the state name. We can use an assembler to instruct Spring on which methods and properties to expose as MBean operations and attributes. In this blog entry, I'll focus on two of these approaches: the method name approach and the interface-based approach.

Spring allows us to specify which methods we want exposed on a bean-exposed-as-MBean. The Spring XML configuration if file to make this happen is shown again with the changes to make this happen on lines 20 and 24-31.

spring-jmx-config.xml (using JMX MBean Exporter MethodNameBasedMBeanInfoAssembler)
<?xml version="1.0" encoding="UTF-8"?>
<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.0.xsd">

   <bean id="stateHandler"
         class="dustin.StateHandler">
      <property name="state" value="Colorado" />
      <property name="capital" value="Denver" />
   </bean>

   <bean class="org.springframework.jmx.export.MBeanExporter">
      <property name="beans">
         <map>
            <entry key="dustin.example:name=jmx,type=spring"
                   value-ref="stateHandler" />
         </map>
      </property>

      <property name="assembler" ref="assembler" />

   </bean>

   <bean id="assembler"
         class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
      <property name="managedMethods">
         <list>
            <value>setState</value>
         </list>
      </property>
   </bean>

   <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
      <property name="port" value="1099"/>
   </bean>

   <bean id="serverConnector"
         class="org.springframework.jmx.support.ConnectorServerFactoryBean"
         depends-on="registry">
      <property name="objectName" value="connector:name=rmi"/>
      <property name="serviceUrl"
                value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
   </bean>

</beans>

When this new Spring XML configuration file is used, the exposed MBean operations are limited to what is specified in the Method Name Bean Assembler. The output when using JConsole now to monitor the application is shown next.

We only exposed a single setState method name in our configuration above. So why are there two setState operations in the JConsole window? The explanation for this is that Spring has no way of knowing which setState method is being specified because it cannot differentiate between these overloaded methods based on method name alone. The next approach, using a Java interface to specify which methods are exposed, will work better in this situation.

The following Spring XML configuration uses an interface-based approach rather than a method name approach to narrow which methods and properties Spring should expose as MBean operations and attributes. Again, the relevant new XML code is on line 20 and lines 24 through 31.

spring-jmx-config.xml (using JMX MBean Exporter InterfaceBasedMBeanInfoAssembler)
<?xml version="1.0" encoding="UTF-8"?>
<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.0.xsd">

   <bean id="stateHandler"
         class="dustin.StateHandler">
      <property name="state" value="Colorado" />
      <property name="capital" value="Denver" />
   </bean>

   <bean class="org.springframework.jmx.export.MBeanExporter">
      <property name="beans">
         <map>
            <entry key="dustin.example:name=jmx,type=spring"
                   value-ref="stateHandler" />
         </map>
      </property>

      <property name="assembler" ref="assembler" />

   </bean>

   <bean id="assembler"
         class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
      <property name="managedInterfaces">
         <list>
            <value>dustin.AppStateIf</value>
         </list>
      </property>
   </bean>

   <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
      <property name="port" value="1099"/>
   </bean>

   <bean id="serverConnector"
         class="org.springframework.jmx.support.ConnectorServerFactoryBean"
         depends-on="registry">
      <property name="objectName" value="connector:name=rmi"/>
      <property name="serviceUrl"                value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
   </bean>
</beans>

Note that a Java interface is specified in the above XML. This very simple interface is shown next.

AppStateIf.java
package dustin;

/**
 * Interface to be used to narrow operations exposed as MBean operations on a
 * bean-turned-MBean via Spring.
 * 
 * @author Dustin
 */
public interface AppStateIf
{
   public void setState(final int aState);
}

When this newly configured Spring-powered application is run with this interface-based JMX exposure, JConsole displays the results shown in the next snapshot.

With the interface-based approach we were able to instruct Spring more specifically on which of the overloaded methods we wanted exposed as an MBean operation. Note that we did not even need to have the POJO class StateHandler implement this interface (though it is often nice to do so to allow the compiler to ensure that a POJO that will be exposed as an MBean provides anticipated methods for JMX operations). Our regular Java object could remain untouched and this interface could be applied in the Spring configuration only to narrow down the exposed JMX operations.

Another common way to configure Spring to expose certain Java methods as JMX operations and certain Java properties as JMX attributes is to use Spring-expected annotations in the source code. Unlike the method name approach and interface-based approach shown in this blog entry, this approach does require Spring-specific annotations to be added to your previously non-Spring-specific Java classes. The advantage gained for this price, however, is the ability to even more finely control what is exposed as an MBean and to provide significantly more descriptive information to the exposed MBean, its attributes, and its operations.

One final note should be made about the Spring XML configuration examples shown in this entry. In all three entries, I specified RMI server connection information and instructed Spring to set up an RMI registry to support remote JMX features. This remote support was not necessary to use the examples, however, because I used JConsole on the same host as the managed application and both (JConsole and the managed application) were run under my same user ID.

No comments: