Monday, March 3, 2008

Using Spring Metadata MBean Exporting for Greater Descriptive Detail

I typically use InterfaceBasedMBeanInfoAssembler approach when configuring the Spring Framework to expose specific operations and attributes on my beans-turned-MBeans. However, there are times when the ability to easily associate metadata with a bean class can be useful in order to provide greater detail (MBeanInfo data) to the JMX client (such as JConsole). This blog entry demonstrates how easy this is to accomplish with Spring and how the results look in JConsole. Along the way, a few nuances of interest will also be pointed out.

One disadvantage of the in-source metadata approach is that the beans which can be simple Java classes when using the interface-based approach must now have Spring-specific annotations applied to their source code. The benefit that comes for this price is the ability to instruct Spring to set much more of the generated Model MBean's metadata.

The following class, ManageMe, shows an otherwise normal Java class that has the Spring-specific annotations (JDK annotations in this case, though Commons Attributes could have been used instead).

ManageMe.java

package jmx.spring.dustin;

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

/**
* This class's main purpose is to test and showcase Spring 2.5's support for
* annotations-based MBean exposure.
*/
@ManagedResource(
objectName="dustin:name=springJmxWithAnnotations",
description="Example and test of Spring's support for annotations-based detail")
public class ManageMe
{
private String manageableString;
private boolean manageableBoolean;
private boolean anotherBoolean;

@ManagedAttribute(description="The manageableString Attribute")
public String getManageableString()
{
return this.manageableString;
}

@ManagedAttribute(description="Set the manageableString Attribute")
public void setManageableString(String string)
{
this.manageableString = string;
}

@ManagedAttribute(description="Get the manageableBoolean Attribute")
public boolean isManageableBoolean()
{
return this.manageableBoolean;
}

@ManagedAttribute(description="Set the manageableBoolean Attrbiute")
public void setManageableBoolean(boolean bool)
{
this.manageableBoolean = bool;
}

@ManagedAttribute(description="Get value of another boolean")
public boolean isAnotherBoolean()
{
return this.anotherBoolean;
}

@ManagedOperation(description="Setter for another boolean")
@ManagedOperationParameters({
@ManagedOperationParameter(name="bool",description="Setter for another boolean") })
public void setAnotherBoolean(boolean bool)
{
this.anotherBoolean = bool;
}

@ManagedOperation(description="Textbook Hello World example.")
public String helloWorld()
{
return "Hello World!";
}

@ManagedOperation(description="Slightly more interesting variant of Hello World")
@ManagedOperationParameters({
@ManagedOperationParameter(name="subject",description="Subject of Hello") })
public String helloWorld(String subject)
{
return "Hello " + subject;
}
}


I set the bean class ManageMe up intentionally to expose multiple attributes and operations for comparison purposes. I intentionally annotated one of the attribute accessor methods (setAnotherBoolean) with the @ManagedOperation annotation rather than the normally used @ManagedAttribute annotation. For the other attributes, I used the proper @ManagedAttribute. I also used @ManagedOperation on real operations that are not getter or setter methods for an attribute.

The Spring XML configuration file I used for this is shown next.

spring.xml

<?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="beanTurnedMBean"
class="jmx.spring.dustin.ManageMe">
<description>A JMX Service</description>
<property name="manageableString" value="springXmlDefault" />
<property name="manageableBoolean" value="false" />
</bean>

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

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

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

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

</beans>


The most interesting aspect of this XML file for our use is MBeanExporter's use of MetadataMBeanInfoAssembler and its use of AnnotationJmxAttributeSource.

Now, it is time to look at this managed bean using JConsole and see the extra provided data. Click on any screen snapshot to see a larger version of that screen snapshot.

The first screen snapshot (below) shows that the managedString attribute is both writable and readable. This is because the @ManagedAttribute annotation was applied to both the setter and getter method for that attribute in the ManageMe class shown above.



Note in the screen snapshot of JConsole shown above that the description is also shown. While the Readable and Writable fields are true in this String attribute case, the Is field is false.

The next screen snapshot (below) demonstrates the JConsole output for AnotherBoolean. In this case, the field Readable is still true, but Writable is false. This is because I "mistakenly" applied an @ManagedOperation attribute to the setter for AnotherBoolean rather than the appropriate @ManagedAttribute annotation. Note also that the Is field is marked true because this is a boolean and the getter is correctly annotated.



The final screen snapshot (below) displays what JConsole looks like for a boolean that has both its getter (or is method) and setter set correctly with @ManagedAttribute. As you can see in the snapshot, all three fields (Readable, Writable, and Is) are true in this case.



In all three of the above screen snapshots, you will note that there is no setAnotherBoolean listed because of the problem of misapplying @ManagedOperation to a setter method. The other getters and setters are there because the @ManagedAttribute annotation was properly applied to them. For completeness, you can note that @ManagedOperation does work for true operations that are not actually getters or setters for attributes. The two helloWorld methods prove this.

In summary, Spring's support for metadata-configurated MBean exporting is easy to apply to garner extra exposed MBean detail. One of the most important take-aways from this blog entry is that @ManagedAttribute must be used on getter and setter methods and that @ManagedOperation should be used on methods that are not getters or setters for any attributes.

One thing I did not show here is that if neither annotation is applied to a getter or setter method, it is not shown as an operation in JConsole. In other words, the only way to get a getter or setter method to show up is to use @ManagedAttribute for each piece (once for the getter and once for the setter). If you want a read-only attribute from a JMX client perspective, apply @ManagedAttribute to only the getter. If you want a write-only, apply that annotation only to the setter method.

Chapter 20 of the Spring 2.5 reference focuses on Spring's JMX support. Of particular interest related to this blog entry are sections 20.3.3 ("Using JDK 5.0 Annotations") and 20.3.4 ("Source-Level Metadata Types"). The latter section includes Table 20.2 ("Source-Level Metadata Types") which confirms that @ManagedAttribute should be applied to methods that are getters and setters for attributes while @ManagedOperation should be applied to all methods other than getters and setters.

5 comments:

hilal said...

hey Dustin,

I have a problem with JMS.
spring can export a managedOperation if it has a managed parameter. I can see these operations via jconsole and use them.

But it does not work with the methods which do not have any parameters.

what is the problem, I don't understand.

thanks for help, in advance


hilal

Dustin said...

hilal,

You should first ensure that none of your methods that don't accept parameters also don't start with "get" or "set" in their names. In my blog entry, I showed the example of helloWorld() method call without parameters. If your methods that don't expect any parameters do not start with "get" or "set" and still do not show up in JConsole, you could post a short snippet of example code here or at the Spring JMX forum in case someone can see the problem.

hilal said...

hello Dustin,

my code is so.

this operation can be exported:

@ManagedOperation(description = "Get AttributeDataTypes")
@ManagedOperationParameters( { @ManagedOperationParameter(name = "identityLikeness", description = "identityLikeness") })
public Map getAttributeDataTypes(String identityLikeness) {
PersistenceSupportInterface support = Supporter.persistenceSupport;
return support.searchByIdentity(AttrDataType.class, identityLikeness, "identity", "systemId");
}


but this can't:

@ManagedOperation(description = "Get AttributeDataTypes")
public Map getAttributeDataTypes() {
PersistenceSupportInterface support = Supporter.persistenceSupport;
return support.searchByIdentity(AttrDataType.class, null, "identity", "systemId");
}

ps: I tried to post it to spring threads, but it said that until 15 post I can't send any :(

so i need your help

Dustin said...

hilal,

This does appear to be a problem because your method name starts with "get" and because you aren't passing any parameters. The one that worked probably did so because the parameters implied that this was not a true "get"/accessor method. However, your method whose name starts with "get" and does not accept any parameters meets all the requirements of a "getter"/accessor method and so Spring JMX wants to treat this like an @ManagedAttribute rather than as an @ManagedOperation. I believe your second case will work fine for you if you change the name to not have "get" in it. For example, if you rename the method obtainAttributeDataTypes(), provideAttributeDataTypes(), acquireAttributeDataTypes(), or something similar, I believe it will work for you. This all boils down to the fact that Spring/JMX wants to treat any method that begins with "get" and accepts no parameters as an attribute rather than as an operation.

hilal said...

yes Dustin,

you are absolutely right; i don't know how i could make such a mistake, even I read about it.

thanks a lot