Chapter 20. JMX

20.1. Introduction

The JMX support in Spring provides you with the features to easily and transparently integrate your Spring application into a JMX infrastructure.

Specifically, Spring's JMX support provides four core features:

  • The automatic registration of any Spring bean as a JMX MBean

  • A flexible mechanism for controlling the management interface of your beans

  • The declarative exposure of MBeans over remote, JSR-160 connectors

  • The simple proxying of both local and remote MBean resources

These features are designed to work without coupling your application components to either Spring or JMX interfaces and classes. Indeed, for the most part your application classes need not be aware of either Spring or JMX in order to take advantage of the Spring JMX features.

20.2. Exporting your beans to JMX

The core class in Spring's JMX framework is the MBeanExporter. This class is responsible for taking your Spring beans and registering them with a JMX MBeanServer. For example, consider the following class:

package org.springframework.jmx;

public class JmxTestBean implements IJmxTestBean {

    private String name;
    private int age;
    private boolean isSuperman;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int add(int x, int y) {
        return x + y;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }
}

To expose the properties and methods of this bean as attributes and operations of an MBean you simply configure an instance of the MBeanExporter class in your configuration file and pass in the bean as shown below:

<beans>

  <!-- this bean must not be lazily initialized if the exporting is to happen -->
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

The pertinent bean definition from the above configuration snippet is the exporter bean. The beans property tells the MBeanExporter exactly which of your beans must be exported to the JMX MBeanServer. In the default configuration, the key of each entry in the beans Map is used as the ObjectName for the bean referenced by the corresponding entry value. This behavior can be changed as described in the section entitled Section 20.4, “Controlling the ObjectNames for your beans”.

With this configuration the testBean bean is exposed as an MBean under the ObjectName bean:name=testBean1. By default, all public properties of the bean are exposed as attributes and all public methods (bar those inherited from the Object class) are exposed as operations.

20.2.1. Creating an MBeanServer

The above configuration assumes that the application is running in an environment that has one (and only one) MBeanServer already running. In this case, Spring will attempt to locate the running MBeanServer and register your beans with that server (if any). This behavior is useful when your application is running inside a container such as Tomcat or IBM WebSphere that has itss own MBeanServer.

However, this approach is of no use in a standalone environment, or when running inside a container that does not provide an MBeanServer. To address this you can create an MBeanServer instance declaratively by adding an instance of the org.springframework.jmx.support.MBeanServerFactoryBean class to your configuration. You can also ensure that a specific MBeanServer is used by setting the value of the MBeanExporter's server property to the MBeanServer value returned by an MBeanServerFactoryBean; for example:

<beans>

  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>

  <!--
    this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
    this means that it must not be marked as lazily initialized
  -->
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="server" ref="mbeanServer"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

Here an instance of MBeanServer is created by the MBeanServerFactoryBean and is supplied to the MBeanExporter via the server property. When you supply your own MBeanServer instance, the MBeanExporter will not attempt to locate a running MBeanServer and will use the supplied MBeanServer instance. For this to work correctly, you must (of course) have a JMX implementation on your classpath.

20.2.2. Reusing an existing MBeanServer

If no server is specified, the MBeanExporter tries to automatically detect a running MBeanServer. This works in most environment where only one MBeanServer instance is used, however when multiple instances exist, the exporter might pick the wrong server. In such cases, one should use the MBeanServer agentId to indicate which instance to be used:

<beans>
   <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
     <!-- indicate to first look for a server -->
     <property name="locateExistingServerIfPossible" value="true"/>
     <!-- search for the MBeanServer instance with the given agentId -->
     <property name="agentId" value="<MBeanServer instance agentId>"/>
   </bean>
   
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
     <property name="server" ref="mbeanServer"/>
   ...
   </bean>
</beans>

For platforms/cases where the existing MBeanServer has a dynamic (or unknown) agentId which is retrieved through lookup methods, one should use factory-method:

<beans>
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
     <property name="server">
       <!-- Custom MBeanServerLocator -->
       <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
     </property>
     
     <!-- other beans here -->
     
   </bean>
</beans>

20.2.3. Lazy-initialized MBeans

If you configure a bean with the MBeanExporter that is also configured for lazy initialization, then the MBeanExporter will not break this contract and will avoid instantiating the bean. Instead, it will register a proxy with the MBeanServer and will defer obtaining the bean from the container until the first invocation on the proxy occurs.

20.2.4. Automatic registration of MBeans

Any beans that are exported through the MBeanExporter and are already valid MBeans are registered as-is with the MBeanServer without further intervention from Spring. MBeans can be automatically detected by the MBeanExporter by setting the autodetect property to true:

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  <property name="autodetect" value="true"/>
</bean>

<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>

Here, the bean called spring:mbean=true is already a valid JMX MBean and will be automatically registered by Spring. By default, beans that are autodetected for JMX registration have their bean name used as the ObjectName. This behavior can be overridden as detailed in the section entitled Section 20.4, “Controlling the ObjectNames for your beans”.

20.2.5. Controlling the registration behavior

Consider the scenario where a Spring MBeanExporter attempts to register an MBean with an MBeanServer using the ObjectName 'bean:name=testBean1'. If an MBean instance has already been registered under that same ObjectName, the default behavior is to fail (and throw an InstanceAlreadyExistsException).

It is possible to control the behavior of exactly what happens when an MBean is registered with an MBeanServer. Spring's JMX support allows for three different registration behaviors to control the registration behavior when the registration process finds that an MBean has already been registered under the same ObjectName; these registration behaviors are summarized on the following table:

Table 20.1. Registration Behaviors

Registration behaviorExplanation

REGISTRATION_FAIL_ON_EXISTING

This is the default registration behavior. If an MBean instance has already been registered under the same ObjectName, the MBean that is being registered will not be registered and an InstanceAlreadyExistsException will be thrown. The existing MBean is unaffected.

REGISTRATION_IGNORE_EXISTING

If an MBean instance has already been registered under the same ObjectName, the MBean that is being registered will not be registered. The existing MBean is unaffected, and no Exception will be thrown.

This is useful in settings where multiple applications want to share a common MBean in a shared MBeanServer.

REGISTRATION_REPLACE_EXISTING

If an MBean instance has already been registered under the same ObjectName, the existing MBean that was previously registered will be unregistered and the new MBean will be registered in its place (the new MBean effectively replaces the previous instance).

The above values are defined as constants on the MBeanRegistrationSupport class (the MBeanExporter class derives from this superclass). If you want to change the default registration behavior, you simply need to set the value of the registrationBehaviorName property on your MBeanExporter definition to one of those values.

The following example illustrates how to effect a change from the default registration behavior to the REGISTRATION_REPLACE_EXISTING behavior:

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="registrationBehaviorName" value="REGISTRATION_REPLACE_EXISTING"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

20.3. Controlling the management interface of your beans

In the previous example, you had little control over the management interface of your bean; all of the public properties and methods of each exported bean was exposed as JMX attributes and operations respectively. To exercise finer-grained control over exactly which properties and methods of your exported beans are actually exposed as JMX attributes and operations, Spring JMX provides a comprehensive and extensible mechanism for controlling the management interfaces of your beans.

20.3.1. The MBeanInfoAssembler Interface

Behind the scenes, the MBeanExporter delegates to an implementation of the org.springframework.jmx.export.assembler.MBeanInfoAssembler interface which is responsible for defining the management interface of each bean that is being exposed. The default implementation, org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler, simply defines a management interface that exposes all public properties and methods (as you saw in the previous examples). Spring provides two additional implementations of the MBeanInfoAssembler interface that allow you to control the generated management interface using either source-level metadata or any arbitrary interface.

20.3.2. Using source-Level metadata

Using the MetadataMBeanInfoAssembler you can define the management interfaces for your beans using source level metadata. The reading of metadata is encapsulated by the org.springframework.jmx.export.metadata.JmxAttributeSource interface. Out of the box, Spring JMX provides support for two implementations of this interface: org.springframework.jmx.export.metadata.AttributesJmxAttributeSource for Commons Attributes and org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource for JDK 5.0 annotations. The MetadataMBeanInfoAssembler must be configured with an implementation instance of the JmxAttributeSource interface for it to function correctly (there is no default). For the following example, we will use the Commons Attributes metadata approach.

To mark a bean for export to JMX, you should annotate the bean class with the ManagedResource attribute. In the case of the Commons Attributes metadata approach this class can be found in the org.springframework.jmx.metadata package. Each method you wish to expose as an operation must be marked with the ManagedOperation attribute and each property you wish to expose must be marked with the ManagedAttribute attribute. When marking properties you can omit either the annotation of the getter or the setter to create a write-only or read-only attribute respectively.

The example below shows the JmxTestBean class that you saw earlier marked with Commons Attributes metadata:

package org.springframework.jmx;

/**
 * @@org.springframework.jmx.export.metadata.ManagedResource
 *  (description="My Managed Bean", objectName="spring:bean=test",
 *  log=true, logFile="jmx.log", currencyTimeLimit=15, persistPolicy="OnUpdate",
 *  persistPeriod=200, persistLocation="foo", persistName="bar")
 */
public class JmxTestBean implements IJmxTestBean {

  private String name;

  private int age;


  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   *   (description="The Age Attribute", currencyTimeLimit=15)
   */
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   *  (description="The Name Attribute",  currencyTimeLimit=20,
   *   defaultValue="bar", persistPolicy="OnUpdate")
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   *   (defaultValue="foo", persistPeriod=300)
   */
  public String getName() {
    return name;
  }

  /**
   * @@org.springframework.jmx.export.metadata.ManagedOperation
   *  (description="Add Two Numbers Together")
   */
  public int add(int x, int y) {
    return x + y;
  }

  public void dontExposeMe() {
    throw new RuntimeException();
  }
}

Here you can see that the JmxTestBean class is marked with the ManagedResource attribute and that this ManagedResource attribute is configured with a set of properties. These properties can be used to configure various aspects of the MBean that is generated by the MBeanExporter, and are explained in greater detail later in section entitled Section 20.3.4, “Source-Level Metadata Types”.

You will also notice that both the age and name properties are annotated with the ManagedAttribute attribute, but in the case of the age property, only the getter is marked. This will cause both of these properties to be included in the management interface as attributes, but the age attribute will be read-only.

Finally, you will notice that the add(int, int) method is marked with the ManagedOperation attribute whereas the dontExposeMe() method is not. This will cause the management interface to contain only one operation, add(int, int), when using the MetadataMBeanInfoAssembler.

The code below shows how you configure the MBeanExporter to use the MetadataMBeanInfoAssembler:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="assembler" ref="assembler"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="attributeSource"
        class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource">
    <property name="attributes">
      <bean class="org.springframework.metadata.commons.CommonsAttributes"/>
    </property>
  </bean>

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

</beans>

Here you can see that an MetadataMBeanInfoAssembler bean has been configured with an instance of the AttributesJmxAttributeSource class and passed to the MBeanExporter through the assembler property. This is all that is required to take advantage of metadata-driven management interfaces for your Spring-exposed MBeans.

20.3.3. Using JDK 5.0 Annotations

To enable the use of JDK 5.0 annotations for management interface definition, Spring provides a set of annotations that mirror the Commons Attribute attribute classes and an implementation of the JmxAttributeSource strategy interface, the AnnotationsJmxAttributeSource class, that allows the MBeanInfoAssembler to read them.

The example below shows a bean where the management interface is defined by the presence of JDK 5.0 annotation types:

package org.springframework.jmx;

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

@ManagedResource(objectName="bean:name=testBean4", description="My Managed Bean", log=true,
    logFile="jmx.log", currencyTimeLimit=15, persistPolicy="OnUpdate", persistPeriod=200,
    persistLocation="foo", persistName="bar")
public class AnnotationTestBean implements IJmxTestBean {

  private String name;
  private int age;

  @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @ManagedAttribute(description="The Name Attribute",
      currencyTimeLimit=20,
      defaultValue="bar",
      persistPolicy="OnUpdate")
  public void setName(String name) {
    this.name = name;
  }

  @ManagedAttribute(defaultValue="foo", persistPeriod=300)
  public String getName() {
    return name;
  }

  @ManagedOperation(description="Add two numbers")
  @ManagedOperationParameters({
    @ManagedOperationParameter(name = "x", description = "The first number"),
    @ManagedOperationParameter(name = "y", description = "The second number")})
  public int add(int x, int y) {
    return x + y;
  }

  public void dontExposeMe() {
    throw new RuntimeException();
  }
}

As you can see little has changed, other than the basic syntax of the metadata definitions.

<beans>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="assembler" ref="assembler"/>
        <property name="namingStrategy" ref="namingStrategy"/>
        <property name="autodetect" value="true"/>
    </bean>

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

    <!-- will create management interface using annotation metadata -->
    <bean id="assembler"
          class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <!-- will pick up the ObjectName from the annotation -->
    <bean id="namingStrategy"
          class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

20.3.4. Source-Level Metadata Types

The following source level metadata types are available for use in Spring JMX:

Table 20.2. Source-Level Metadata Types

PurposeCommons Attributes AttributeJDK 5.0 AnnotationAttribute / Annotation Type
Mark all instances of a Class as JMX managed resourcesManagedResource@ManagedResourceClass
Mark a method as a JMX operationManagedOperation@ManagedOperationMethod
Mark a getter or setter as one half of a JMX attributeManagedAttribute@ManagedAttributeMethod (only getters and setters)
Define descriptions for operation parametersManagedOperationParameter@ManagedOperationParameter and @ManagedOperationParametersMethod

The following configuration parameters are available for use on these source-level metadata types:

Table 20.3. Source-Level Metadata Parameters

ParameterDescriptionApplies to
ObjectNameUsed by MetadataNamingStrategy to determine the ObjectName of a managed resourceManagedResource
descriptionSets the friendly description of the resource, attribute or operationManagedResource, ManagedAttribute, ManagedOperation, ManagedOperationParameter
currencyTimeLimitSets the value of the currencyTimeLimit descriptor fieldManagedResource, ManagedAttribute
defaultValueSets the value of the defaultValue descriptor fieldManagedAttribute
logSets the value of the log descriptor fieldManagedResource
logFileSets the value of the logFile descriptor fieldManagedResource
persistPolicySets the value of the persistPolicy descriptor fieldManagedResource
persistPeriodSets the value of the persistPeriod descriptor fieldManagedResource
persistLocationSets the value of the persistLocation descriptor fieldManagedResource
persistNameSets the value of the persistName descriptor fieldManagedResource
nameSets the display name of an operation parameterManagedOperationParameter
indexSets the index of an operation parameterManagedOperationParameter

20.3.5. The AutodetectCapableMBeanInfoAssembler interface

To simplify configuration even further, Spring introduces the AutodetectCapableMBeanInfoAssembler interface which extends the MBeanInfoAssembler interface to add support for autodetection of MBean resources. If you configure the MBeanExporter with an instance of AutodetectCapableMBeanInfoAssembler then it is allowed to "vote" on the inclusion of beans for exposure to JMX.

Out of the box, the only implementation of the AutodetectCapableMBeanInfo interface is the MetadataMBeanInfoAssembler which will vote to include any bean which is marked with the ManagedResource attribute. The default approach in this case is to use the bean name as the ObjectName which results in a configuration like this:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <!-- notice how no 'beans' are explicitly configured here -->
    <property name="autodetect" value="true"/>
    <property name="assembler" ref="assembler"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <!-- (for Commons Attributes-based metadata) -->
  <bean id="attributeSource"
        class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource">
    <property name="attributes">
      <bean class="org.springframework.metadata.commons.CommonsAttributes"/>
    </property>
  </bean>
  
  <!-- (for Java 5+ annotations-based metadata) -->
  <!--
  <bean id="attributeSource"
        class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
  -->

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

</beans>

Notice that in this configuration no beans are passed to the MBeanExporter; however, the JmxTestBean will still be registered since it is marked with the ManagedResource attribute and the MetadataMBeanInfoAssembler detects this and votes to include it. The only problem with this approach is that the name of the JmxTestBean now has business meaning. You can address this issue by changing the default behavior for ObjectName creation as defined in the section entitled Section 20.4, “Controlling the ObjectNames for your beans”.

20.3.6. Defining management interfaces using Java interfaces

In addition to the MetadataMBeanInfoAssembler, Spring also includes the InterfaceBasedMBeanInfoAssembler which allows you to constrain the methods and properties that are exposed based on the set of methods defined in a collection of interfaces.

Although the standard mechanism for exposing MBeans is to use interfaces and a simple naming scheme, the InterfaceBasedMBeanInfoAssembler extends this functionality by removing the need for naming conventions, allowing you to use more than one interface and removing the need for your beans to implement the MBean interfaces.

Consider this interface that is used to define a management interface for the JmxTestBean class that you saw earlier:

public interface IJmxTestBean {

  public int add(int x, int y);

  public long myOperation();

  public int getAge();

  public void setAge(int age);

  public void setName(String name);

  public String getName();
}

This interface defines the methods and properties that will be exposed as operations and attributes on the JMX MBean. The code below shows how to configure Spring JMX to use this interface as the definition for the management interface:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean5" value-ref="testBean"/>
      </map>
    </property>
    <property name="assembler">
      <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
        <property name="managedInterfaces">
          <value>org.springframework.jmx.IJmxTestBean</value>
        </property>
      </bean>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

Here you can see that the InterfaceBasedMBeanInfoAssembler is configured to use the IJmxTestBean interface when constructing the management interface for any bean. It is important to understand that beans processed by the InterfaceBasedMBeanInfoAssembler are not required to implement the interface used to generate the JMX management interface.

In the case above, the IJmxTestBean interface is used to construct all management interfaces for all beans. In many cases this is not the desired behavior and you may want to use different interfaces for different beans. In this case, you can pass InterfaceBasedMBeanInfoAssembler a Properties instance via the interfaceMappings property, where the key of each entry is the bean name and the value of each entry is a comma-separated list of interface names to use for that bean.

If no management interface is specified through either the managedInterfaces or interfaceMappings properties, then the InterfaceBasedMBeanInfoAssembler will reflect on the bean and use all of the interfaces implemented by that bean to create the management interface.

20.3.7. Using MethodNameBasedMBeanInfoAssembler

The MethodNameBasedMBeanInfoAssembler allows you to specify a list of method names that will be exposed to JMX as attributes and operations. The code below shows a sample configuration for this:

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean5" value-ref="testBean"/>
      </map>
    </property>
    <property name="assembler">
      <bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
        <property name="managedMethods">
          <value>add,myOperation,getName,setName,getAge</value>
        </property>
      </bean>
    </property>
</bean>

Here you can see that the methods add and myOperation will be exposed as JMX operations and getName(), setName(String) and getAge() will be exposed as the appropriate half of a JMX attribute. In the code above, the method mappings apply to beans that are exposed to JMX. To control method exposure on a bean-by-bean basis, use the methodMappings property of MethodNameMBeanInfoAssembler to map bean names to lists of method names.

20.4. Controlling the ObjectNames for your beans

Behind the scenes, the MBeanExporter delegates to an implementation of the ObjectNamingStrategy to obtain ObjectNames for each of the beans it is registering. The default implementation, KeyNamingStrategy, will, by default, use the key of the beans Map as the ObjectName. In addition, the KeyNamingStrategy can map the key of the beans Map to an entry in a Properties file (or files) to resolve the ObjectName. In addition to the KeyNamingStrategy, Spring provides two additional ObjectNamingStrategy implementations: the IdentityNamingStrategy that builds an ObjectName based on the JVM identity of the bean and the MetadataNamingStrategy that uses source level metadata to obtain the ObjectName.

20.4.1. Reading ObjectNames from Properties

You can configure your own KeyNamingStrategy instance and configure it to read ObjectNames from a Properties instance rather than use bean key. The KeyNamingStrategy will attempt to locate an entry in the Properties with a key corresponding to the bean key. If no entry is found or if the Properties instance is null then the bean key itself is used.

The code below shows a sample configuration for the KeyNamingStrategy:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="testBean" value-ref="testBean"/>
      </map>
    </property>
    <property name="namingStrategy" ref="namingStrategy"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
    <property name="mappings">
      <props>
        <prop key="testBean">bean:name=testBean1</prop>
      </props>
    </property>
    <property name="mappingLocations">
      <value>names1.properties,names2.properties</value>
    </property>
  </bean

</beans>

Here an instance of KeyNamingStrategy is configured with a Properties instance that is merged from the Properties instance defined by the mapping property and the properties files located in the paths defined by the mappings property. In this configuration, the testBean bean will be given the ObjectName bean:name=testBean1 since this is the entry in the Properties instance that has a key corresponding to the bean key.

If no entry in the Properties instance can be found then the bean key name is used as the ObjectName.

20.4.2. Using the MetadataNamingStrategy

The MetadataNamingStrategy uses the objectName property of the ManagedResource attribute on each bean to create the ObjectName. The code below shows the configuration for the MetadataNamingStrategy:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="testBean" value-ref="testBean"/>
      </map>
    </property>
    <property name="namingStrategy" ref="namingStrategy"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

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

  <bean id="attributeSource"
      class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"/>

</beans>

If no objectName has been provided for the ManagedResource attribute, then an ObjectName will be created with the following format: [fully-qualified-package-name]:type=[short-classname],name=[bean-name]. For example, the generated ObjectName for the following bean would be: com.foo:type=MyClass,name=myBean.

<bean id="myBean" class="com.foo.MyClass"/>

20.4.3. The <context:mbean-export/> element

If you are using at least Java 5, then a convenience subclass of MBeanExporter is available: AnnotationMBeanExporter. When defining an instance of this subclass, the namingStrategy, assembler, and attributeSource configuration is no longer needed, since it will always use standard Java annotation-based metadata (autodetection is always enabled as well). In fact, an even simpler syntax is supported with the inclusion of Spring's 'context' namespace in Spring 2.5. Rather than defining an MBeanExporter bean, provide this single element:

<context:mbean-export/>

You can provide a reference to a particular MBean server if necessary, and the defaultDomain attribute (a property of AnnotationMBeanExporter) accepts an alternate value for the generated MBean ObjectNames' domains. This would be used in place of the fully qualified package name as described in the previous section on MetadataNamingStrategy.

<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
.
[Note]Note

Do not use interface-based AOP proxies in combination with autodetection of JMX annotations in your bean classes. Interface-based proxies 'hide' the target class, which also hides the JMX managed resource annotations. Hence, use target-class proxies in that case: through setting the 'proxy-target-class' flag on <aop:config/>, <tx:annotation-driven/>, etc. Otherwise, your JMX beans might be silently ignored at startup...

20.5. JSR-160 Connectors

For remote access, Spring JMX module offers two FactoryBean implementations inside the org.springframework.jmx.support package for creating both server- and client-side connectors.

20.5.1. Server-side Connectors

To have Spring JMX create, start and expose a JSR-160 JMXConnectorServer use the following configuration:

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>

By default ConnectorServerFactoryBean creates a JMXConnectorServer bound to "service:jmx:jmxmp://localhost:9875". The serverConnector bean thus exposes the local MBeanServer to clients through the JMXMP protocol on localhost, port 9875. Note that the JMXMP protocol is marked as optional by the JSR 160 specification: currently, the main open-source JMX implementation, MX4J, and the one provided with J2SE 5.0 do not support JMXMP.

To specify another URL and register the JMXConnectorServer itself with the MBeanServer use the serviceUrl and ObjectName properties respectively:

<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:1099/myconnector"/>
</bean>

If the ObjectName property is set Spring will automatically register your connector with the MBeanServer under that ObjectName. The example below shows the full set of parameters which you can pass to the ConnectorServerFactoryBean when creating a JMXConnector:

<bean id="serverConnector"
      class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=iiop"/>
  <property name="serviceUrl" 
               value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
  <property name="threaded" value="true"/>
  <property name="daemon" value="true"/>
  <property name="environment">
    <map>
      <entry key="someKey" value="someValue"/>
    </map>
  </property>
</bean>

Note that when using a RMI-based connector you need the lookup service (tnameserv or rmiregistry) to be started in order for the name registration to complete. If you are using Spring to export remote services for you via RMI, then Spring will already have constructed an RMI registry. If not, you can easily start a registry using the following snippet of configuration:

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

20.5.2. Client-side Connectors

To create an MBeanServerConnection to a remote JSR-160 enabled MBeanServer use the MBeanServerConnectionFactoryBean as shown below:

<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi://localhost:9875"/>
</bean>

20.5.3. JMX over Burlap/Hessian/SOAP

JSR-160 permits extensions to the way in which communication is done between the client and the server. The examples above are using the mandatory RMI-based implementation required by the JSR-160 specification (IIOP and JRMP) and the (optional) JMXMP. By using other providers or JMX implementations (such as MX4J) you can take advantage of protocols like SOAP, Hessian, Burlap over simple HTTP or SSL and others:

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=burlap"/>
  <property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
</bean>

In the case of the above example, MX4J 3.0.0 was used; see the official MX4J documentation for more information.

20.6. Accessing MBeans via Proxies

Spring JMX allows you to create proxies that re-route calls to MBeans registered in a local or remote MBeanServer. These proxies provide you with a standard Java interface through which you can interact with your MBeans. The code below shows how to configure a proxy for an MBean running in a local MBeanServer:

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
    <property name="objectName" value="bean:name=testBean"/>
    <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>

Here you can see that a proxy is created for the MBean registered under the ObjectName: bean:name=testBean. The set of interfaces that the proxy will implement is controlled by the proxyInterfaces property and the rules for mapping methods and properties on these interfaces to operations and attributes on the MBean are the same rules used by the InterfaceBasedMBeanInfoAssembler.

The MBeanProxyFactoryBean can create a proxy to any MBean that is accessible via an MBeanServerConnection. By default, the local MBeanServer is located and used, but you can override this and provide an MBeanServerConnection pointing to a remote MBeanServer to cater for proxies pointing to remote MBeans:

<bean id="clientConnector"
      class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
  <property name="objectName" value="bean:name=testBean"/>
  <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
  <property name="server" ref="clientConnector"/>
</bean>

Here you can see that we create an MBeanServerConnection pointing to a remote machine using the MBeanServerConnectionFactoryBean. This MBeanServerConnection is then passed to the MBeanProxyFactoryBean via the server property. The proxy that is created will forward all invocations to the MBeanServer via this MBeanServerConnection.

20.7. Notifications

Spring's JMX offering includes comprehensive support for JMX notifications.

20.7.1. Registering Listeners for Notifications

Spring's JMX support makes it very easy to register any number of NotificationListeners with any number of MBeans (this includes MBeans exported by Spring's MBeanExporter and MBeans registered via some other mechanism). By way of an example, consider the scenario where one would like to be informed (via a Notification) each and every time an attribute of a target MBean changes.

package com.example;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

public class ConsoleLoggingNotificationListener
               implements NotificationListener, NotificationFilter {

    public void handleNotification(Notification notification, Object handback) {
        System.out.println(notification);
        System.out.println(handback);
    }

    public boolean isNotificationEnabled(Notification notification) {
        return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
    }
}
<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListenerMappings">
      <map>
        <entry key="bean:name=testBean1">
          <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
      </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

With the above configuration in place, every time a JMX Notification is broadcast from the target MBean (bean:name=testBean1), the ConsoleLoggingNotificationListener bean that was registered as a listener via the notificationListenerMappings property will be notified. The ConsoleLoggingNotificationListener bean can then take whatever action it deems appropriate in response to the Notification.

You can also use straight bean names as the link between exported beans and listeners:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListenerMappings">
      <map>
        <entry key="testBean">
          <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
      </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

If one wants to register a single NotificationListener instance for all of the beans that the enclosing MBeanExporter is exporting, one can use the special wildcard '*' (sans quotes) as the key for an entry in the notificationListenerMappings property map; for example:

<property name="notificationListenerMappings">
  <map>
    <entry key="*">
      <bean class="com.example.ConsoleLoggingNotificationListener"/>
    </entry>
  </map>
</property>

If one needs to do the inverse (that is, register a number of distinct listeners against an MBean), then one has to use the notificationListeners list property instead (and in preference to the notificationListenerMappings property). This time, instead of configuring simply a NotificationListener for a single MBean, one configures NotificationListenerBean instances... a NotificationListenerBean encapsulates a NotificationListener and the ObjectName (or ObjectNames) that it is to be registered against in an MBeanServer. The NotificationListenerBean also encapsulates a number of other properties such as a NotificationFilter and an arbitrary handback object that can be used in advanced JMX notification scenarios.

The configuration when using NotificationListenerBean instances is not wildly different to what was presented previously:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListeners">
        <list>
            <bean class="org.springframework.jmx.export.NotificationListenerBean">
                <constructor-arg>
                    <bean class="com.example.ConsoleLoggingNotificationListener"/>
                </constructor-arg>
                <property name="mappedObjectNames">
                    <list>
                        <value>bean:name=testBean1</value>
                    </list>
                </property>
            </bean>
        </list>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

The above example is equivalent to the first notification example. Lets assume then that we want to be given a handback object every time a Notification is raised, and that additionally we want to filter out extraneous Notifications by supplying a NotificationFilter. (For a full discussion of just what a handback object is, and indeed what a NotificationFilter is, please do consult that section of the JMX specification (1.2) entitled 'The JMX Notification Model'.)

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean1"/>
        <entry key="bean:name=testBean2" value-ref="testBean2"/>
      </map>
    </property>
    <property name="notificationListeners">
        <list>
            <bean class="org.springframework.jmx.export.NotificationListenerBean">
                <constructor-arg ref="customerNotificationListener"/>
                <property name="mappedObjectNames">
                    <list>
                        <!-- handles notifications from two distinct MBeans -->
                        <value>bean:name=testBean1</value>
                        <value>bean:name=testBean2</value>
                    </list>
                </property>
                <property name="handback">
                    <bean class="java.lang.String">
                        <constructor-arg value="This could be anything..."/>
                    </bean>
                </property>
                <property name="notificationFilter" ref="customerNotificationListener"/>
            </bean>
        </list>
    </property>
  </bean>
  
  <!-- implements both the NotificationListener and NotificationFilter interfaces -->
  <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>

  <bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="ANOTHER TEST"/>
    <property name="age" value="200"/>
  </bean>

</beans>

20.7.2. Publishing Notifications

Spring provides support not just for registering to receive Notifications, but also for publishing Notifications.

[Note]Note

Please note that this section is really only relevant to Spring managed beans that have been exposed as MBeans via an MBeanExporter; any existing, user-defined MBeans should use the standard JMX APIs for notification publication.

The key interface in Spring's JMX notification publication support is the NotificationPublisher interface (defined in the org.springframework.jmx.export.notification package). Any bean that is going to be exported as an MBean via an MBeanExporter instance can implement the related NotificationPublisherAware interface to gain access to a NotificationPublisher instance. The NotificationPublisherAware interface simply supplies an instance of a NotificationPublisher to the implementing bean via a simple setter method, which the bean can then use to publish Notifications.

As stated in the Javadoc for the NotificationPublisher class, managed beans that are publishing events via the NotificationPublisher mechanism are not responsible for the state management of any notification listeners and the like ... Spring's JMX support will take care of handling all the JMX infrastructure issues. All one need do as an application developer is implement the NotificationPublisherAware interface and start publishing events using the supplied NotificationPublisher instance. Note that the NotificationPublisher will be set after the managed bean has been registered with an MBeanServer.

Using a NotificationPublisher instance is quite straightforward... one simply creates a JMX Notification instance (or an instance of an appropriate Notification subclass), populates the notification with the data pertinent to the event that is to be published, and one then invokes the sendNotification(Notification) on the NotificationPublisher instance, passing in the Notification.

Find below a simple example... in this scenario, exported instances of the JmxTestBean are going to publish a NotificationEvent every time the add(int, int) operation is invoked.

package org.springframework.jmx;
			
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;

public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {

    private String name;
    private int age;
    private boolean isSuperman;
    private NotificationPublisher publisher;

    // other getters and setters omitted for clarity

    public int add(int x, int y) {
        int answer = x + y;
        this.publisher.sendNotification(new Notification("add", this, 0));
        return answer;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }
    
    public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.publisher = notificationPublisher;
    }
}

The NotificationPublisher interface and the machinery to get it all working is one of the nicer features of Spring's JMX support. It does however come with the price tag of coupling your classes to both Spring and JMX; as always, the advice here is to be pragmatic... if you need the functionality offered by the NotificationPublisher and you can accept the coupling to both Spring and JMX, then do so.

20.8. Further Resources

This section contains links to further resources about JMX.