Thursday, July 29, 2010

JMX with Spring and Annotation (Part 1)

I was working on migrating a standalone server to Spring when I started looking into Spring's JMX support.  And then I found how convienent it is to use Annotation to define MBeans instead of creating MBean interfaces.

First of all, I got rid of the old MBean interface:
ServerMBean.java
package com.my.company.server;

import java.io.IOException;

public interface ServerMBean {

    public String getName() throws IOException;

    public String getVersion() throws IOException;

    public long getChannelCount() throws IOException;

    public void shutdown() throws IOException;

    public void sendMessage(String message) throws IOException;

    public String[] getLoggers() throws IOException;

    public String getLogLevel(String category) throws IOException;

    public void setLogLevel(String category, String level) throws IOException;
}
And rewrite the Server class like:
package com.my.company.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName="ChannelServer:type=Server", description="Server")
public class Server {

protected Logger log = Logger.getLogger(getClass());

private String name;
private String version;
private long uptime;
private int channelCount;

protected Server(String name, String version) {
    this.name = name;
    this.version = version;
}

@ManagedAttribute(description="Server Name")
public String getName() {
    return name;
}

@ManagedAttribute(description="Server Version")
public String getVersion() {
    return version;
}

public long getUpTime() {
return uptime;
}

@ManagedAttribute(description="Channel Count")
public long getChannelCount() throws IOException {
    return this.channelCount;
}

@ManagedOperation(description="Shutdown Server")
public void shutdown() throws IOException {
    stop();
}

@ManagedOperation(description="Send a message")
@ManagedOperationParameters({
    @ManagedOperationParameter(name="msg", description="message")
})
public void sendMessage(String msg) throws IOException {
    //blah blah
}

@ManagedOperation(description="Set log level to the given category")
@ManagedOperationParameters({
    @ManagedOperationParameter(name="category", description="category"),
    @ManagedOperationParameter(name="level", description="log level")
})
public void setLogLevel(String category, String level) throws IOException {
    log.warn("Setting log level " + level + " for " + category);
    Level logLevel = Level.toLevel(level);
    LogManager.getLogger(category).setLevel(logLevel);
}

@ManagedOperation(description="Log level for given category")
@ManagedOperationParameters({
    @ManagedOperationParameter(name="category", description="category")
})
public String getLogLevel(String category) throws IOException {
    return LogManager.getLogger(category).getLevel().toString();
}

@ManagedOperation(description="List of loggers")
public String[] getLoggers() throws IOException {
    Enumeration<?> loggers = LogManager.getCurrentLoggers();
    ArrayList<String> loggerList = new ArrayList<String>();
    while (loggers.hasMoreElements()) {
        Logger logger = (Logger)loggers.nextElement();
        loggerList.add(logger.getName());
    }
    Collections.sort(loggerList);
    return loggerList.toArray(new String[0]);
}

public final void start() {
    log.info("Starting channel server...");
    try {
        //blah blah
        uptime = System.currentTimeMillis();
    } catch (Exception ex) {
        log.fatal("Error starting channel server", ex);
        stop();
    }
}

public final void stop() {
    log.info("Shutting down channel server...");
    try {
        //blah blah
    } catch (Throwable e) {
        log.error("error stopping container", e);
        System.exit(1);
    }
    log.info("Shutdown completed");
    System.exit(0);
}
}
Now let's moving on to Spring configuration. First, define the MBeanExporter:
<bean name="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" autowire="no">
<property name="autodetect" value="true"/>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
</property>
<property name="namingStrategy">
<bean class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
</property>
</bean>
An much simpler alternative:
<beans default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    ...
    <context:mbean-export/>
</beans>

No comments: