Last night I took a few moments to dig around in the Spring sandbox (that's in Spring's CVS for those of you who don't know) and got to play around with the JMX stuff in there. It seems to be fairly complete already, but I suppose we won't see it in mainline Spring until 1.2. Nevertheless, it's mostly usable, so I thought I'd share my experience with you, my faithful blog reader.
(Besides, I've not blogged anything in several weeks, so I needed something to keep this going.)
To start, nothing I show you here will be available in any downloadable version of Spring you may find. If you want to try this stuff for yourself, you'll need to check Spring out of CVS and build it and the sandbox stuff for yourself.
With that said...the very first thing I did was to create ManagedBeanImpl.java:
public class ManagedBeanImpl implements ManagedBean {
public ManagedBeanImpl() {}
private String someProperty;
public void setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
public String doSomething() {
return "Here's some property: "+someProperty;
}
}
This is just a plain ol' JavaBean...nothing special. It implements an interface that exposes the doSomething() method, but that has little bearing on this example. The key thing to note is that this is just a plain bean...not a true JMX MBean.
The next thing I did was declare this bean in my Spring configuration file:
<bean id="managedBean"
class="com.habuma.jmx.ManagedBeanImpl">
<property name="someProperty"><value>Howdy</value></property>
</bean>
Again, nothing too special. Just a regular Spring bean declaration.
But here's where the JMX stuff starts. I declared the following bean:
<bean id="jmxAdapter"
class="org.springframework.jmx.JmxMBeanAdapter"
depends-on="jmxServer"
>
<property name="beans">
<map>
<entry key="foobar:Name=myBean">
<ref bean="managedBean"/>
</entry>
</map>
</property>
</bean>
JmxMBeanAdapter
takes a java.util.Map
of bean references and exposes their properties and methods as managed properties and managed operations of an MBean...automagically. The key of each map entry is used to create the ObjectName
. You may notice the depends-on
setting. I'll explain this in a bit, so hold on.
This is all you need if your app is running in a context that has its own JMX implementation. But, if you're running standalone, you'll need to start your own MBeanServer
. The following declaration does that for you:
<bean id="jmxServer"
class="org.springframework.jmx.factory.MBeanServerFactoryBean">
<property name="defaultDomain"><value>foobar</value></property>
</bean>
Note that you'll need Sun's jmxri.jar to make this work.
Now it's time for me to explain the depends-on
setting I used when declaring the "jmxAdapter" bean. If you're running a standalone app (i.e., not in a context that has its own JMX implementation), then this setting tells Spring not to create the the "jmxAdapter" until the "jmxServer" has been created. This is important because JmxMBeanAdapter
will attempt to retrieve an MBeanServer
to register its MBeans. If the "jmxServer" bean hasn't been kicked off yet, then an exception will be thrown because it won't find the MBeanServer
. (If you're not running standalone, then you won't need this depends-on
nor will you need the "jmxServer" declaration.)
All this is great fun, but it's even more fun to see that the "managedBean" bean is registered as an MBean. To do that, you need Sun's jmxtools.jar in your classpath so that you can use their HtmlAdaptorServer
(isn't it annoying how they misspelled "adapter"?). To make it easy to wire in an HtmlAdaptorServer
, I wrote the following Spring factory bean:
package com.habuma.jmx;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import com.sun.jdmk.comm.HtmlAdaptorServer;
public class HtmlAdapterFactoryBean
implements FactoryBean, InitializingBean {
HtmlAdaptorServer htmlAdapter;
public Object getObject() throws Exception {
return htmlAdapter;
}
public Class getObjectType() {
return HtmlAdaptorServer.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() throws Exception {
htmlAdapter = new HtmlAdaptorServer();
htmlAdapter.setPort(port);
ObjectName objectName = new ObjectName(name);
mBeanServer.registerMBean(htmlAdapter, objectName);
htmlAdapter.start();
}
private int port = 9092;
public void setPort(int port) {
this.port = port;
}
private String name = "HtmlAdaptor:name=HtmlAdaptor";
public void setName(String name) {
this.name = name;
}
private MBeanServer mBeanServer;
public void setMBeanServer(MBeanServer mBeanServer) {
this.mBeanServer = mBeanServer;
}
}
This factory bean goes through the trouble of setting up and starting the HtmlAdaptorServer
. To use it, declare it in the Spring configuration file as follows:
<bean id="htmlAdapter"
class="com.habuma.jmx.HtmlAdapterFactoryBean">
<property name="mBeanServer">
<ref bean="jmxServer"/>
</property>
</bean>
The only thing you need to give to HtmlAdapterFactoryBean
is a reference to the "jmxServer" bean. But you can also choose an alternate port or name used to create the HtmlAdaptorServer
's ObjectName
.
The only thing you need to do is start everything up with the following lines of code:
ApplicationContext context =
new ClassPathXmlApplicationContext("jmx-app.xml");
At this point point your browser at http://localhost:9092/. You should see a list of MBeans. Find the bean in the "foobar" domain named "myBean" and click on its link. You should see the "someProperty" property (and be able to change it) and you should see the "doSomething" operation and be able to execute it.
There's a lot more you can do with Spring's JMX support. For example, we saw that the MBean name came from the key of its map entry. But that's only because JmxMBeanAdapter
has a default naming strategy that KeyNamingStrategy
. But you could plug in a different naming strategy and change how MBeans are named. For example, there is a MetadataNamingStrategy
that can pull the name from source-level metadata on the bean.
Also, as declared above, all properties of the bean are exposed as managed properties and all methods exposed as managed operations. But that's only because JmxMBeanAdapter
's default assembler is ReflectiveModelMBeanInfoAssembler
. If you were to plugin MetadataModelMBeanInfoAssembler
, then you can use source-level metadata to declare which properties and methods are exposed on the MBean.
And I've not even mentioned how to access these MBeans using proxies.
But, this blog entry is getting too long as it is. Maybe I'll share more about Spring's JMX stuff in a later entry. But for those of you who have read this far, I'm using this blog entry to spill the beans (so to speak) that for the last few months I've been co-authoring Spring in Action for Manning. This has been a fun project because I really dig the Spring framework. I liked writing XDoclet in Action because XDoclet helped me work around the annoying parts of J2EE. But I like Spring because Spring helps me skip the annoying parts altogether.
I'll be wrapping up the writing phase of Spring in Action in the next week. That'll free up some of my time to blog some more and I'll try to share more of the stuff I find fun with Spring. Stay tuned.