Modify Spring Beans and Log4j levels on a running server with JMX and JConsole

1

JMX (Java Management Extensions) is around for quite a
while. But since Java 1.5 the JDK is bundled with JConsole, a JMX client, which
will hopefully boost the popularity of JMX.  It’s a bit hard to explain what JMX is in a
few words so I quoted Sun: “Java
Management Extensions (JMX) technology provides the tools for building
distributed, Web-based, modular and dynamic solutions for managing and
monitoring devices, applications, and service-driven networks.
”

In this article I will give a quick introduction to JConsole
and show you how to use JMX to manage your Spring beans remotely and finally I
will show you how to change the logging levels. With remote bean management you
can change the logging levels of your application while your server is running,
change the database you use or disable parts of your application to decrease
the load on your server.....

Using JConsole with
Tomcat

My first encounter with JMX was with Tomcat. We had some
memory problems and didn’t know where to look for them. We enabled JMX on
Tomcat and plugged into Tomcat with JConsole. 
JConsole is a JMX client and is delivered with your 1.5 JDK.

It’s located in the bin directory of your JDK. With JConsole
we could see when memory using was increasing and take measures.

 

You need to set some environmental variables to enable JMX.
Don’t set these values in your global environmental variables because each JVM
needs his own port number.

Add the following line to catalina.bat (in your Tomcat bin
directory):

set
JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote.port=1099
 
-Dcom.sun.management.jmxremote.authenticate=false

Or Catalina.sh for *nix users:

JAVA_OPTS="$JAVA_OPTS
-Dcom.sun.management.jmxremote.port=1099
 
-Dcom.sun.management.jmxremote.authenticate=false"

 

Don’t forget to include JAVA_OPTS after the equals sign;
otherwise you will override existing settings like memory allocation. Now start
your Tomcat with the startup command.

 

JMX is now accessible with JConsole. Start JConsole and
click on the tab remote (it’s also possible to use local, but JConsole uses a
lot of resources so it’s better to use it from a different pc, especially when
you want to monitor performance)

Copy the details of the dialog window above and click
connect.

You’ll now receive a load of information with some nice
charts. When you have the memory window open long enough you will see when the
garbage collector paid Tomcat a visit. You can even call the garbage collector
yourself with the Perform GC button.

 

 

Creating an MBean in
Spring

Of course the charts are nice, but JMX can do a lot more. I
read things about MBeans in the Java Magazine of the NLJUG (Dutch Java User
Group) and came up with the idea to make Spring Beans Spring MBeans.

MBean stands for managed bean (don’t confuse it with JSF
backing beans!). MBeans are connection points for a JMX client. You can manage
your application, device or any other resource. The idea I came up with was
changing the database on a running server or the logging levels on the fly.

But first a simple Hello World sample to get to know JMX a
little bit better. This sample is how I started with JMX, it’s not the right
way to do it, but gives you a basic understanding of what you can do with JMX.

I assume you understand how Spring WebMVC work (otherwise
visit http://technology.amis.nl/blog/?p=1149
)

 

I created an AbstractController with a property String:


package nl.amis.controller;

public class HelloWorldController extends AbstractController 
            implements HelloWorldControllerMBean {
    private String string;

    protected ModelAndView handleRequestInternal(HttpServletRequest request, 
            HttpServletResponse responsethrows Exception {
        return new ModelAndView("success""string", string);
    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }
}

As you can see the controller implements HelloWorldControllerMBean. Because the
name of the interface ends with MBean the implementing class can be registered
as MBean. Create the interface:


public interface HelloWorldControllerMBean {

    public void setString(String s);

    public String getString();

}

 

Unfortunately Java
isn’t smart enough the register the bean as an MBean; we have to do that
manually. Create the following constructor:


public HelloWorldController() throws Exception {

    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

    ObjectName name = new  ObjectName("nl.amis.controller:type=HelloWorldController");

    mbs.registerMBean(this, name);

}

 

Now create a file called success.jsp that prints the String

<c:out value="${string}"/>

)

It’s time to deploy the application. When you visit the url
for the Controller nothing is displayed.

Let’s open JConsole like described earlier. Click on the tab
MBeans and you should see the package nl.amis.controller:

 

 

When you click on the Controller you can change the String.
When you do this it will immediately change in you application.

 

Changing properties is not the only thing you can do. It’s
also possible to invoke methods and get the return values. Add a method public
int returnCount(); to the HelloWorldControllerMBean. Implement this method in
HelloWorldController:


public int returnCount(){

    return counter;

}

Also create a counter member in HelloWorldController and add
count++; to the handleRequestInternal method. This will increase the counter
every time the page is visited.

 

Now redeploy your application and go the
HelloWorldController MBean the tab operations is not grayed out anymore. Click
on the tab and hit the button. It will return the count value.

 

 

Creating an MBean in
Spring, the right way

After finishing this example I thought that it wasn’t really
Spring-like to register your bean in the constructor. So I visited the Spring
homepage and grabbed a book and started looking for JMX. After a while I found
out that is was possible. The examples didn’t work (I think the package names
changed), but after some fixing I got it working. It’s possible to expose a
random bean as an MBean.

Add the following bean to your [servletname]-servlet.xml (defining the exporter
in other xml files gave strange results)


<bean id="exporter"

     class="org.springframework.jmx.export.MBeanExporter">

     <property name="beans">

          <map>

              <entry key="bean:name=helloMBean"

                   value-ref="/hello.htm" />

          </map>

     </property>

</bean>

 

Remove the implements HelloWorldControllerMBean and remove
the constructor of HelloWorldController. 
Now the HelloWorldController bean is an MBean and is accessible under
the element bean in JConsole

 

 

Now all the methods in the bean are exposed and that’s
something that you might not want.

 

Using JMX with Log4j

In the Log4j is an org.apache.log4j.jmx package. I hoped
this would save me some work, but the only documentation is disappointing:
“This package lets you manage log4j settings using JMX. It is unfortunately not
of production quality.”

So we have to do some work ourselves. First add a logger to
the HelloWorldController:


private static final Logger log = Logger.getLogger(HelloWorldController.class);

After that add the following lines of logging to the handleRequestInternal
method:


log.debug("debug");

log.info("info");

log.error("error");

log.fatal("fatal");

 

The next step is creating an MBean that changes the logging
level:


public class Log4JManager {

    public void setLogger(String packageName, String level) {

        LogManager.getLogger(packageName).setLevel(Level.toLevel(level));

    }

}

 

Add an entry to the previously created map in Spring:


<entry key="bean:name=logBean" value-ref="logManager" />

And the last step is adding the bean to your Spring
configuration:


<bean name="logManager" class="nl.amis.Log4JManager"/>

 

That’s all! That Log4j should work with JMX natively is
nice, but this is already quite easy.

When we run the Controller all four logging statements will
be printed. Now go to JConsole, MBeans tab and go to bean/logBean and change
nl.amis to error:

 

The last step is checking the Controller again. It now only
prints the error and fatal statement.

 

 

I waited a while before changing the levels so you could see
the difference.

 

Conclusion

I think I only scratched the surface of JMX with this
article. There are a lot more possibilities with JMX. So I think I have to
order a book about JMX. It can be really useful in any project.

 

Sources

http://java.sun.com/products/JavaManagement/

http://wiki.jboss.org/wiki/Wiki.jsp?page=UseJDK5JConsole

http://java.sun.com/j2se/1.5.0/docs/guide/jmx/tutorial/tutorialTOC.html

http://www.springframework.org/docs/reference/jmx.html

http://jroller.com/page/klejs?entry=log4j_and_jmx

Share.

About Author