6.17.2009

JMX Management for fun and profit - Act 2

So now that we have caused an ample amount of trouble with our App Servers messing around with all those internal MBeans, stopping servers, etc.; I think it is time to move on to Act 2, in which our hero creates a custom MBean!

This is where things start to get interesting. Let's say for arguments sake, that you have a cache that you would like to be able to invalidate from anywhere. This will be the premise for our first custom MBean.

Our first order of business is to create our "Cache" object that we want to manage. Normally this would exist already, however, for the sake of education, I will create one here first.

UserCache.java

package mbeans;

/**
* Before you all go crazy about this should be a singleton and it is not threadsafe,
* let me just point out that this is for all intents and purposes a "mock-object"
* designed to prove a point. That is all.
*/
public class UserCache {
// For simplicity, I will make this package-private so the management wrapper
// can access it easily.
Map userCache;

public UserCache() {
this.userCache = new HashMap();
}

public User getUser(String username) {
User u = userCache.get(username);
if ( u == null ) {
u = UserDaoFactory.getUserDao().loadUser(username);
userCache.put(username, u);
}
return u;
}
}


The next step is to create our MBean Interface. JMX Spec says that an MBean interface and implementation should follow a strict naming convention where the MBean interface is the managed objects name followed by MBean. So for our example, we are going to create a Manager object called UserCacheManager, thus, our interface shall be named (in the same package) UserCacheManagerMBean.

Let's take a look at our MBean interface.

UserCacheManagerMBean.java

package mbeans;

public interface UserCacheManagerMBean {
void invalidateCache();
}


Pretty simple and straight forward if I had to say so myself. Next we will create our management object, the UserCacheManager.


package mbeans;

public class UserCacheManager implements UserCacheManagerMBean {
private UserCache managedCache;

public UserCacheManager(UserCache managedCache) {
this.managedCache = managedCache;
}

public void invalidateCache() {
managedCache.userCache = new HashMap();
}
}


So far, so good. The only thing we have left to do is register it with our MBeanServer. If you are running an application server, you can just about guarantee that one has already been created and has a ton of beans registered in it already. Bearing that in mind, let's count on that for the purpose of this example and register our mbean with the default server.

This next class will act as the Factory object for getting a UserCache, and will be the point of entry from our entire program, so we will let it handle the registering of the management object.

UserCacheFactory.java

package mbeans;

public class UserCacheFactory {
private static final UserCacheFactory instance = new UserCacheFactory();
public static UserCacheFactory getInstance() { return instance; }

private UserCache userCache;

public synchronized UserCache getUserCache() {
if ( userCache == null ) {
userCache = new UserCache();
registerManagedCache();
}
return userCache;
}

private registerManagedCache() {
MBeanServer server = null;

List initialServers = MBeanServerFactory.findMBeanServer(null);
if ( initialServers != null && initialServers.size() > 0 ) {
server = initialServers.get(0);
}

if ( server == null ) {
server = MBeanServerFactory.createMBeanServer();
}

try {
ObjectName mbeanName = new ObjectName("Application:impl=UserCacheManager");
server.registerMBean( managedCache, mbeanName );
catch (Throwable t) {
t.printStackTrace();
}
}


Let's look at what we are doing in the above code.

First off, we made it a little more threadsafe with a singleton factory that has a synchronized getUserCache() method.

Second, if we are constructing a new UserCache when we access it from the factory, we also create the manager bean for the class at the same time and register it with the MBeanServer.

Let's take a second to talk about this mysterious new ObjectName object that I am creating for this MBean.

ObjectName is an object that represents the namespace that a bean will live in once it is registered with the MBeanServer. This is how any clients or agents will know of this MBean. The namespacing is made up of two main parts, delimited by a colon. The first part is the Domain in which the MBean lives - This should be somewhat specific to your application. The second part of the ObjectName string is a set of key=val pairs delimited by commas. For example, key1=val1,key2=val2.

If you were to browse in jConsole for our new UserCacheManager, you would find it listed under:

+ Application
+ UserCacheManager


If you were to lookup the MBean by it's name you would look it up using the same string that we passed into the ObjectName constructor.

Voila! We have created our first custom MBean and have registered it with our default MBeanServer instance. If you run this code in your App Server, you should be able to connect to the server's JMX port with jConsole and see the manager object appear in the MBeans tab shortly after you access the method UserCacheFactory.getInstance().getUserCache() for the first time in your application.

Stay tuned in Act 3 as we explore the different types of MBeans that you can create aside from the above demonstrated standard MBean. We will look at MXBeans, Open MBeans, Dynamic MBeans, and Model MBeans; see examples of each; and determine the positives and negatives of using each type.



1 comment: