Many web applications have an admin or tools component, which allow the application administrator to get information about the application internals and to update its configuration properties to change application behavior without having to take down the application or write/modify code or properties. Recently I needed to write such a tool.
The tool would provide a form that would allow the super-user/admin type to update a Configuration bean properties. This bean controlled the behavior of a front end controller, so changing the Configuration bean properties would automatically change the behavior of the controller. I had originally envisioned the tool as a standard Spring FormController with a set of JSP pages, but the more I thought about it, it seemed like JMX (Java Management eXtension) would be a more appropriate choice here.
I had never actually used JMX before this, but I had read about it in this JMX book by Lindfors, Fleury and the JBoss Group, and I remembered that the two JMX implementations covered in the book came with HTTP based JMX Consoles. My plan was to somehow hook up the JMX console to my application so it could be used to update the Configuration bean properties.
To check out the feasibility of this approach, I decided to do a small proof of concept, a rather lame calculator web application. It has a single page which is accessible via a HTTP GET request. The GET request provides two integer parameters, which are then added by the calculator and the results displayed on the web page. The actual operator that the calculator uses (add, subtract, multiply or divide) is provided by the CalculatorConfig bean, which is a Managed bean that should be visible to the JMX console. The web application is a Spring application. Spring provides very good integration with JMX (as it does with a variety of other J2EE technologies). For the JMX implementation, I chose Sun's JMX-RI reference implementation, mainly because its HtmlAdaptor (aka the HTML Console) is all Java (no JSP) and comes pre-packaged in the jmxtools.jar file, so its easy to embed.
The calculator webapp contains all the usual suspects. The incoming request is handled by the CalculatorController (an implementation of the Spring Controller interface), which calls the CalculatorService with operands passed in on the request parameters. The CalculatorService is injected with the CalculatorConfig which implements the standard MBean interface CalculatorConfigMBean. The MBean suffix is needed if you are using standard MBeans, that is the only way the MBean server figures out if a bean is manageable. I could have dispensed with the interface and made CalculatorConfig a managed bean by renaming it with an MBean suffix, but I figured that in a real application, I may not have the luxury of changing class names. The Spring configuration for the webapp side is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" > <beans> <bean id="calculatorConfig" class="my.lame.calculator.beans.CalculatorConfig"> <property name="operation" value="+" /> </bean> <bean id="calculatorService" class="my.lame.calculator.services.CalculatorService"> <property name="calculatorConfig" ref="calculatorConfig" /> </bean> <bean id="calculatorController" class="my.lame.calculator.controllers.CalculatorController"> <property name="calculatorService" ref="calculatorService" /> </bean> <bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="calculator.do">calculatorController</prop> </props> </property> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> </bean> ...
The JMX side involves setting up an MBeanServer and registering our CalculatorConfig bean (which is an MBean since it implements a standard MBean), and the HtmlAdaptor from JMX-RI, which is also an MBean, and starting the HtmlAdaptor. This creates a JMX console listening on port 8082 (which you can configure differently by setting the port property on the HtmlAdaptor bean). Both the HtmlAdaptor and the CalculatorConfig MBeans are declared as regular beans in the Spring applicationContext.xml file. The MBeanServerFactoryBean creates the embedded MBeanServer, and the two MBeans are registered into the server using the MBeanExporter bean. The embedded MBeanServer approach is optional, since most application servers now have JMX consoles built in, we could register our MBeans into the application server directly as well. However, embedding the MBeanServer within our application is probably more generic and guaranteed to work with any application server. Here is the JMX side of the Spring configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
... <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean" /> <bean id="htmlAdaptor" class="com.sun.jdmk.comm.HtmlAdaptorServer" init-method="start" /> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="adaptor:name=htmlAdaptor" value-ref="htmlAdaptor" /> <entry key="bean:name=calculatorConfigBean" value-ref="calculatorConfig" /> </map> </property> <property name="server" ref="mbeanServer" /> </bean> </beans>
The MBeanServer setup was a little complicated. I got as far as setting up the MBeanServer and the MBeanExporter with the Spring reference, but I had to go look at the BaseAgent.java code in the JMX Tutorial (provided with the JMX-RI source download) to figure out the part where I could register the HtmlAdaptor MBean supplied with the JMX-RI to the MBeanServer and call start() on it with the bean:@init-method attribute. Another thing to note is that we are currently running the MBeanServer and our HtmlAdaptor client within the same JVM as the managed web application, but it is possible to make the MBeanServer expose a serviceUrl which can be accessed by an external JMX client as explained in this Java.net article by Lu Jian.
The figures below shows the CalculatorConfig MBean in the JMX Console, and the result of the HTTP GET request on the actual webapp. The operator is set to "+", and the result of the HTTP GET request is: "Result of 1 + 2 = 3"
Now I change the operator to "*" in the JMX Console and click Apply. The same HTTP GET request now produces a different result: "Result of 1 * 2 = 2":
The code for the CalculatorController, ControllerService and ControllerConfig beans are trivial, and I am not going to include it here. Basically, the CalculatorController parses out the operands from the HTTP GET request, and passes them to the CalculatorService, which pulls the operator from CalculatorConfig, applies them to the operands and returns a result, which the Controller then forwards to a JSP view.
I thought it was pretty cool that all we had to do was to add two additional JMX JAR files (jmxri.jar and jmxtools.jar) to our classpath, add a few more lines of configuration in Spring's application context, and make the bean we wanted to manage implement an interface that is marked as manageable (by suffixing the MBean to its interface name). And the end result was that we could control the configuration of our application. Granted, the application does not do much, so you could argue that it was probably more work to read and experiment with JMX than slapping on a single JSP page to update the configuration, but now that I know how to use it, there will be no learning curve in the future, and its definitely less work to add bean references to the MBeanExporter in the Spring configuration than it is to build individual JSP pages.
Another complaint may be that the JMX interface cannot be customized, to which I would argue that this is a backend GUI which exposes manageable beans at a fairly low level, and eye-candy is unlikely to mandatory for the user working at this level. What this user wants is to get his job done quickly and efficiently, and the JMX console GUI is more than adequate for this purpose. However, if corporate needs dictate, it is still cheaper and more efficient to build up your own corporate HtmlAdapter MBean which you can drop into any web application on demand.