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:
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.
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 Section 24.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.
Note | |
---|---|
|
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 its 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.
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> </bean> <!-- other beans here --> </beans>
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.
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 Section 24.4, “Controlling the ObjectNames for your beans”.
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 24.1. Registration Behaviors
Registration behavior | Explanation |
---|---|
| This is the default registration behavior. If an |
| If an |
| If an |
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>
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.
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.
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. Spring JMX
provides a default implementation which uses Java annotations, namely
org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource
. The
MetadataMBeanInfoAssembler
must be configured with an implementation instance of
the JmxAttributeSource
interface for it to function correctly (there is no
default).
To mark a bean for export to JMX, you should annotate the bean class with the
ManagedResource
annotation. Each method you wish to expose as an operation must be
marked with the ManagedOperation
annotation and each property you wish to expose must
be marked with the ManagedAttribute
annotation. 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 annotated version of the JmxTestBean
class that you saw
earlier:
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(); } }
Here you can see that the JmxTestBean
class is marked with the ManagedResource
annotation and that this ManagedResource
annotation 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 24.3.3, “Source-Level Metadata Types”.
You will also notice that both the age
and name
properties are annotated with the
ManagedAttribute
annotation, 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 configuration below shows how you configure the MBeanExporter
to use the
MetadataMBeanInfoAssembler
:
<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>
Here you can see that an MetadataMBeanInfoAssembler
bean has been configured with an
instance of the AnnotationJmxAttributeSource
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.
The following source level metadata types are available for use in Spring JMX:
Table 24.2. Source-Level Metadata Types
Purpose | Annotation | Annotation Type |
---|---|---|
Mark all instances of a |
| Class |
Mark a method as a JMX operation |
| Method |
Mark a getter or setter as one half of a JMX attribute |
| Method (only getters and setters) |
Define descriptions for operation parameters |
| Method |
The following configuration parameters are available for use on these source-level metadata types:
Table 24.3. Source-Level Metadata Parameters
Parameter | Description | Applies to |
---|---|---|
| Used by |
|
| Sets the friendly description of the resource, attribute or operation |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the value of the |
|
| Sets the display name of an operation parameter |
|
| Sets the index of an operation parameter |
|
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> <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"> <property name="attributeSource"> <bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/> </property> </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 Section 24.4, “Controlling the ObjectNames for your beans”.
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.
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.
Behind the scenes, the MBeanExporter
delegates to an implementation of the
ObjectNamingStrategy
to obtain ObjectName
s 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
.
You can configure your own KeyNamingStrategy
instance and configure it to read
ObjectName
s 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
.
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.annotation.AnnotationJmxAttributeSource"/> </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"/>
If you prefer using the annotation based approach to define
your management interfaces, 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, rather than defining an MBeanExporter
bean, an even
simpler syntax is supported by the @EnableMBeanExport
@Configuration
annotation.
@Configuration @EnableMBeanExport public class AppConfig { }
If you prefer XML based configuration the 'context:mbean-export'
element serves the
same purpose.
<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
.
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain") @Configuration ContextConfiguration { }
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
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 |
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.
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 the JDK
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>
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/jndi/rmi://localhost:1099/jmxrmi"/> </bean>
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.
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
.
Spring’s JMX offering includes comprehensive support for JMX 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>
Spring provides support not just for registering to receive Notifications
, but also
for publishing Notifications
.
Note | |
---|---|
Please note that this section is really only relevant to Spring managed beans that have
been exposed as MBeans via an |
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 javadocs of 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.
This section contains links to further resources about JMX.