MBeans in Tomcat

MBeans are part of the JMX specification which has been part of J2SE since Java 5. As such, Tomcat like most other servlet containers supports MBeans since version 5.

Configuring Tomcat

In order to access MBeans remotely, some configuration[1] is required. Tomcat provides a number of system arguments that can be used to enable remote JMX access. As with all Tomcat configuration, the following is best placed in $TOMCAT_ROOT/bin/setenv.sh:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=%YOUR_JMX_PORT% 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false

This allows anyone to remotely connect to your Tomcat's MBeans.

If you require secured access, consider the following:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=%YOUR_JMX_PORT% 
-Dcom.sun.management.jmxremote.authenticate=true 
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=%IP address whitelist%
-Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access

User access is managed in $TOMCAT_ROOT/conf/jmxremote.access:

monitorRole readonly  
controlRole readwrite  

And finally, the passwords are stored in $TOMCAT_ROOT/conf/jmxremote.password:

monitorRole tomcat-jmx-pw  
controlRole tomcat-jmx-pw2  

Registering MBeans

The easiest way of registering and unregistering your MBeans is by using a dedicated ServletContextListener whose sole purpose is to take care of your MBeans:

package com.lukaspradel.example.servlet;

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;

import javax.management.InstanceAlreadyExistsException;  
import javax.management.InstanceNotFoundException;  
import javax.management.MBeanRegistrationException;  
import javax.management.MBeanServer;  
import javax.management.MalformedObjectNameException;  
import javax.management.NotCompliantMBeanException;  
import javax.management.ObjectName;  
import javax.servlet.ServletContext;  
import javax.servlet.ServletContextEvent;  
import javax.servlet.ServletContextListener;  
import javax.servlet.annotation.WebListener;

import java.lang.management.ManagementFactory;

@WebListener
public class MBeanContextListener implements ServletContextListener {

    private static final Logger LOG = LoggerFactory.getLogger(MBeanContextListener.class);

    private ObjectName exampleManagementObjectName;

    @Override
    public void contextInitialized(final ServletContextEvent sce) {

        LOG.debug("Register MBeans..");

        final MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

        try {
            exampleManagementObjectName = new ObjectName("com.lukaspradel.example.management:type=ExampleManagement");
            final ExampleManagement examleMbean = new ExampleManagement();
            mbeanServer.registerMBean(examleMbean, exampleManagementObjectName);
        } catch (InstanceAlreadyExistsException | MalformedObjectNameException | MBeanRegistrationException | NotCompliantMBeanException e) {
            LOG.error("Failed to register MBeans!", e);
        }
    }

    @Override
    public void contextDestroyed(final ServletContextEvent sce) {

        LOG.debug("Unregister MBeans..");

        final MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

        try {
            mbeanServer.unregisterMBean(exampleManagementObjectName);
        } catch (MBeanRegistrationException | InstanceNotFoundException e) {
            LOG.error("Failed to unregister MBeans!", e);
        }
    }
}

The callback functions defined by ServletContextListener provide excellent access points for registering and unregistering our MBeans. The boilerplate for the actual MBean handling is plain JMX and very straight-forward.

Calling MBeans

A central MBeanInvoker will serve for demonstration purposes on how to remotely invoke MBean methods.

package com.lukaspradel.example.management;

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;

import javax.enterprise.context.ApplicationScoped;  
import javax.management.JMX;  
import javax.management.MBeanServerConnection;  
import javax.management.ObjectName;  
import javax.management.remote.JMXConnector;  
import javax.management.remote.JMXConnectorFactory;  
import javax.management.remote.JMXServiceURL;

@ApplicationScoped
public class MBeanInvoker {

    private static final Logger LOG = LoggerFactory.getLogger(MBeanInvoker.class);

    private static final String JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:9012/jmxrmi";

    private static final String MBEAN_EXAMPLE_MANAGEMENT = "com.lukaspradel.example.management:type=ExampleManagement";

    private MBeanServerConnection mbsc;

    @PostConstruct
    public void init() {

        try {
            JMXServiceURL url = new JMXServiceURL(JMX_URL);
            JMXConnector jmxc = JMXConnectorFactory.connect(url, null);

            mbsc = jmxc.getMBeanServerConnection();
        } catch (Exception e) {
            LOG.error("Failed to initialize MBeanInvoker!", e);
        }
    }

    public String invokeExampleMBeanExampleMethod(String arg1) throws Exception {

        ObjectName exampleMBeanName = new ObjectName(MBEAN_SESSION_MANAGEMENT);
        ExampleManagementMBean exampleManagementMBean = JMX.newMXBeanProxy(mbsc, exampleMBeanName, ExampleManagementMBean.class, true);

        // not type-safe method
        // Object result = mbsc.invoke(exampleMBeanName, "exampleMethod", new Object[] {arg1}, new String[] {"java.lang.String"});

        String result = exampleManagementMBean.exampleMethod(arg1);

        return result;
    }
}

Again, the code is very straight-forward and self-explaining. We use plain JMX to create an MBeanServerConnection to our correctly configured Tomcat running as localhost at JMX port 9012. What makes JMX truly great is that we can invoke the remote MBeans type-safe using their interfaces. Alternatively, reflection-esque method calling is also supported.

Final remarks

You can manually invoke your MBean methods for testing purposes using the J2SE tool jconsole. Connect to your Tomcat's JMX port by choosing "Remote process" and entering for example localhost:9012. Aside from the tab "MBeans" where you can manually invoke methods with arguments, you will also see other monitoring information like memory usage, Thread info and a JVM summary.

1. http://tomcat.apache.org/tomcat-7.0-doc/monitoring.html