Adding descriptions to JMX beans using an annotation wrapper

When working with standard JMX beans, a constant source of disappointment is the lack of descriptive information about the beans, attributes and operations.

Have a look again at how JConsole displays a bean, from my previous JMX introductory example:

JConsole view of a standard MBean

Now wouldn’t it be nice if it would look more like that:

JConsole view of a fully described MBean

Notice that attribute and operation names are now more meaningful than just some more or less cryptically Java identifiers. Also, the operation parameter to Shutdown now is correctly named, not the absolute meaningless p1 that makes any administrator scratch his head, if he doesn’t have your source code at hand.

And last but not least, there are real descriptions to all the elements in the info table!

What a pity that, though this is part of the JMX standard, it is not possible to achieve it using standard MBeans.

The only possibility is to create a so called Dynamic MBean. But this means implementing a rather complex and abstract interface and a lot of code to write for every bean.

Wouldn’t it be nice to define our example bean in a way like this?

 @JMXBean(description = "My first JMX bean test")
 public class MyBean {
    int level = 0;
    @JMXBeanAttribute(name = "Floor Level", description = "The current floor level")
    public int getLevel() {
        return level;
    }
    @JMXBeanAttribute
    public void setLevel(int newLevel) {
        level = newLevel;
    }
    @JMXBeanOperation(name = "Echo Test", description = "Echoes the parameter back to you")
    public String myMethod(
            @JMXBeanParameter(name = "Input", description = "String of what to echo") String param) {
        return "You said " + param;
    }
 }

Luckily, this is possible with a small wrapper utility that I created, named JMXWrapper.

With it, you can directly define a JMX bean through Java annotations, give descriptive names and add textual descriptions to all elements of a bean.

Notice that there is no interface definition needed anymore, any POJO (Plain Old Java Object) will do, as long as it follows the MXBean constraints, it can be turned into a dynamic JMX bean by simply adding some annotations.

You can map normal methods to attributes or operations. Read-only, write-only and read-write and boolean attributes are supported. Internal setter and getter method names do not have to follow the Java bean standard but can be any method (with one parameter for a setter and no parameter, but a return type for a getter).

And that’s not all; you can even externalize the names and descriptions and use the standard Java ResourceBundle features to localize the names and descriptions.

So it is possible to present the administration interface in the same language your application or server is running with.

Simply specify a standard Java ResourceBundle name in the JMXBean annotation and then specify name and description keys on the elements:

@JMXBean(resourceBundleName="com.example.my.package.BundleName",
    descriptionKey="myBeanDescription")
        public class MyBean {
           int level = 0;
           @JMXBeanAttribute(nameKey="level", descriptionKey="levelDescription")
           public int getLevel() {
               return level;
           }

The wrapper will automatically load locale specific translations from the resource bundle during bean creation time. If no translation can be found, the standard names and descriptions will be used.
And how do you use the wrapper? Simply drop an instance of your annotated normal Java class to the constructor of JMXBeanWrapper and register it with the JMX server:

MyBean bean = new MyBean();
JMXBeanWrapper wrappedBean = new JMXBeanWrapper(bean);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(wrappedBean, new Objectname("com.example.my.package:type=TestBean,name=My Bean"));

That’s all!

I have placed the code of the wrapper on GitHub. Feel free to use it, all feedbacks are welcome. I published it under the MIT Open Source License.

JMX enable your software

When you are writing software that runs in the background, you will sooner or later have the need to monitor and/or administrate it. There are several ways to add this feature to your program, one of the easiest is to use the Java Management eXtension, JMX in short.
JMX is included since Java 1.5 with a lot of extensions in Java 1.6. If you plan to use it, I recommend using at least Java 1.6.
To put it shortly, every Java program has a built-in service (the JMX agent), which allows a separate program (the JMX client) to monitor it and perform administrative tasks. Clients can connect to your program in different ways, allowing local and remote connections. Most professional and public domain administration/monitoring applications now support JMX. There are also bridges between SNMP (Simple Network Management Protocol) and JMX. Java (the JDK) comes with two administration clients, the older JConsole and the newer JVisualVM.

How to JMX enable your software

To enable your software to be managed by JMX clients, you simply have to register one or more so called Managed Beans (MBeans) with the Java engine. Simply said, an MBean is a Java class that gives a JMX client access to methods and data, you implement.
To show this in a working example, I will create a simple Java program that has a state that can be changed by entering a new state string in the console.

(Listing 1, Test server)

package com.udojava.blog.jmx;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class SimpleTestServer {

	private String serverState = "started";

	public String getServerState() {
		return serverState;
	}

	public void setServerState(String serverState) {
		this.serverState = serverState;
	}

	public void startServer() throws IOException {
		String input = "";
		BufferedReader console = new BufferedReader(new InputStreamReader(
				System.in));
		while (!input.equalsIgnoreCase("q")) {
			System.out.println("Enter new state, q to quit:");
			input = console.readLine();
			setServerState(input);
			System.out.println("New state: " + getServerState());
		}
		System.out.println("Aborted from console.");
	}

	public static void main(String[] args) throws IOException {
		SimpleTestServer server = new SimpleTestServer();
		server.startServer();
	}

}

In the next step, I will allow a JMX client to show and change the state and also to shut down the program. I will define a managed bean that will do this all. A managed bean is made up by a Java interface, which defines the contract and a Java class that actually implements the interface.

(Listing 2, Interface)

package com.udojava.blog.jmx;

public interface SimpleTestAdminMBean {
	public void setServerState(String newState);

	public String getServerState();

	public void shutDownServer(String reason);
}

In the interface, I have defined the three operations for reading and writing the server state and for shutting down the server. Note that the shutdown method also has a parameter, where the JMX client can give a reason for the shutdown.

(Listing 3, Bean class)

package com.udojava.blog.jmx;

public class SimpleTestAdmin implements SimpleTestAdminMBean {

	private SimpleTestServer server;

	public SimpleTestAdmin(SimpleTestServer theServer) {
		this.server = theServer;
	}

	@Override
	public void setServerState(String newState) {
		System.out.println("Setting new state through JMX: " + newState);
		server.setServerState(newState);
	}

	@Override
	public String getServerState() {
		return server.getServerState();
	}

	@Override
	public void shutDownServer(String reason) {
		System.out.println("Shutting down through JMX, reason: " + reason);
		/*
		 * Shut down in a separate thread, so that this method
		 * can return before the program exits.
		 */
		new Thread(new Runnable() {

			@Override
			public void run() {
				System.exit(0);
			}
		}).start();
	}

}

Notice that I also defined a constructor that takes the server instance as a parameter, so that I can access the server functionality from the bean. By convention, the bean interface has a name that is the implementation class name plus the extension MBean.
In the last step, I will create and register the managed bean with the built in JMX server. To do this, I add a few lines to the startServer() method of my server class.

(Listing 4, JMX enabled server)

	public void startServer() throws IOException {
		/*
		 * Register JMX bean
		 */
		MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
		ObjectName mxBeanName;
		try {
			mxBeanName = new ObjectName(
					"com.udojava.blog.jmx:type=SimpleTestAdmin,name=Server Info");
			SimpleTestAdmin mxBean = new SimpleTestAdmin(this);
			mbs.registerMBean(mxBean, mxBeanName);
		} catch (Exception e) {
			System.out.println("Error registering JMX");
			e.printStackTrace();
		}

		String input = "";
		BufferedReader console = new BufferedReader(new InputStreamReader(
				System.in));
		while (!input.equalsIgnoreCase("q")) {
			System.out.println("Enter new state, q to quit:");
			input = console.readLine();
			setServerState(input);
			System.out.println("New state: " + getServerState());
		}
		System.out.println("Aborted from console.");
	}

Note that the bean name is not just a simple name string, but an object that is constructed using a domain name, a type and a name. This syntax follows the JMX standard to name managed bean instances.
Testing with a JMX client
First start the demo server, and then go to your Java JDK installation directory. In the bin folder, you will find two JMX clients, JConsole and JVisualVM. When you start them, each one will present you with a list of local running Java programs. Go to the MBeans tab and you can browse there to the defined bean and set/get the server state and shutdown the server from JConsole. With JVisualVM you probably will need to install the MBean plugin first. In that case go to the tools menu, select plugins and available plugins, select the MBeans plugin and follow the instructions.

JConsole MBeans view

Some links for more information:
The Java JMX Tutorial
JVisualVM Home

%d bloggers like this: