Nelz's Blog

Mah blogginess

Spring JMX Challenges

Yesterday I started to do some clean-up work around how we are using Spring and JMX in some of our webapps.

We started off with a very basic configuration:

...
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="beans">
<map>
<entry key="OurApp:name=beanName" value-ref="beanReference"/>
...
</map>
</property>
</bean>
...

By default, this exposes all public methods for management, which is overkill for us. So I wanted to use the Java 5 Annotation capabilities to expose only those methods that are appropriate for management. This brought us to the following configuration:

...
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="assembler" ref="assembler"/>
<property name="beans">
<map>
<entry key="OurApp:name=beanName" value-ref="beanReference"/>
...
</map>
</property>
</bean>
<bean id="jmxAttributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
...

This worked for most of the beans, but when I tried to port this one bean to the new Annotation-based interface definition, I kept getting a nasty stack-trace upon startup, the root of which says "MetadataMBeanInfoAssembler does not support JDK dynamic proxies - export the target beans directly or use CGLIB proxies instead".

I couldn’t figure out for the life of me what the structural difference was between this one bean, and all the others… I tried changing around the other annotations in the class, substituting other beans from the same layer, everything. I couldn’t figure it out.

After a bit, I decided to give up on adding the Annotations to that one bean, but I still wanted to expose the bean. I couldn’t find documentation on this, but it turns out that you are completely able to have multiple "exporters" invoked in your Spring configuration files. This gave me a configuration like this:

...
<bean id="exporter1" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="assembler" ref="assembler"/>
<property name="beans">
<map>
<entry key="OurApp:name=beanName" value-ref="beanReference"/>
...
</map>
</property>
</bean>
<bean id="exporter2" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="beans">
<map>
<entry key="OurApp:name=problemBeanName" value-ref="problemBeanReference"/>
</map>
</property>
</bean>
<bean id="jmxAttributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
...

Great. This worked. I was able to manage the problem bean, and I was able to define the interface to all the other beans via the Annotations. But, when I went to manage the problem bean via the JMX console, there were all sort of dynamic proxy methods exposed…

This got me thinking back to the stack trace:

~MetadataMBeanInfoAssembler does not support JDK dynamic proxies - export the target beans directly or use CGLIB proxies instead

… I definitely see other traces of dynamic proxying in the JMX interface itself.

Then it hit me: AOP. The one thing that was different between the problem bean and all the other beans is that the problem bean is being AOP’ed.

Doing some research on that stack trace error message, I found this discussion. They seem to have figured out there’s a conflict with the MetadataMBeanInfoAssembler and dynamically proxied classes, but I’m still unclear (until further testing) if the suggestion to "set the proxyTargetClass property of your ProxyFactoryBean to true" will work.

In summation, here are the learnings:

  1. MetadataMBeanInfoAssembler is challenged with defining an interface based on Annotations of an AOP’ed class.
  2. It is trivial to define multiple MBeanExporter’s with different configurations if you need to.