2007/11/07

Manageable Spring Application - enable JMX in minutes

We are keeping talking about manageability of an application. Why is it so important? Because at any stage of the application lifecycle, you need a way to probe the some key aspects of the application's internal status and take appropriate actions to change the application's behaviors as a consequence. Without it, you just guess the application's runtime status and never able to steer it per your needs.

But hold on, it's easy to talk about manageability. And it's really a great idea in the air until you want put it in a concrete base in your application. It's so cumbersome, tedious and error prone to make an implementation. There are couples of option you could choose to inject the manageability in your application:

  1. Your own proprietary mechanism.
  2. Standard SNMP
  3. JMX

Forget about option 1, smart as you, don't want invent any wheel. Well, SNMP (Simple Network Management Protocol) sounds good: standardized, bunch of tools and applications and really structured, until you dig in the details of all the ugly OID (Obejct Identification) definitions, binary format, pure data driven approach. And difficulties of debugging. Plus extra cost for those usable commercial SNMP tools and editors.

Fortunately, we are in campus of Java, which is so far the only language and platform that put the serious crucial enterprise aspects intrinsically in the body, especially manageability in an offer as JMX. For the people working for .Net, either they just don't know what is the manageability, or struggling with various proprietary approaches or annoying SNMP stuffs.

Best of the best in Java application manageability is that we have the generous platform MBean Server as a gift from SUN in new version of JVM, which save your efforts looking for a MBean server; we have the JConsole as tool to directly craft your JMX management GUI frontend; and the offer from Spring's JMX supports. Combine them together, you can make any Java application JMX enabled in minutes.

Here is a simple example called MockServer. It's just a simple socket server for any mock testing purpose. With JMX, you can get the information and stop it in runtime.

Following is the partial code snippets, which is definitely the beautiful POJO. No any special MBean or MXBean stuffs in it, see!

/**
* A mock Socket server.
*/
public class MockServer implements Runnable{
private String name="";
private int port = 80;
private boolean bSSL = false;
private int sotimeout = 2*60*1000;//2 minutes

private ServerSocket listeningSocket = null;
private boolean bStop=false;
private long connCounts = 0; //Ongoing total connection counter.

public MockServer(String name, boolean isSSL, int port, int sotimeout){
this.name = name;
this.bSSL = isSSL;
this.port = port;
this.sotimeout = sotimeout;
}

public String getName(){
return name;
}

public int getPort(){
return port;
}

public boolean isSSL(){
return bSSL;
}

public int getSotimeout(){
return sotimeout;
}

public boolean isStopped(){
return bStop;
}

public void stop(){
bStop = true;
try {
listeningSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

protected void createListeningSocket() throws IOException {
ServerSocketFactory factory = (bSSL? SSLServerSocketFactory.getDefault():ServerSocketFactory.getDefault());
listeningSocket = (ServerSocket) factory.createServerSocket(port);
}

public void run(){
System.out.println("Server: "+getName()+" started.");
try {
createListeningSocket();
while (!isStopped()){
Socket worker = listeningSocket.accept();
//Spawn a thread to handle the request.
fork(worker);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (null!=listeningSocket){
try {
listeningSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("Server: "+getName()+" stopped.");
}

protected void fork(Socket worker){
Thread wt = new Thread(new Worker(worker));
wt.start();
}

public long getConnCounts(){
return connCounts;
}
protected synchronized void addCount(){
this.connCounts++;
}
protected synchronized void minusCount(){
this.connCounts--;
}

protected class Worker implements Runnable {
private Socket socket=null;

protected Worker(Socket socket){
this.socket = socket;
}

public void run() {
addCount();
byte[] buf = new byte[1024*10];//10K buffer
InputStream is;
OutputStream os;
try {
//Read request from socket input stream.
is = socket.getInputStream();
int size = is.read(buf);
is.close();
//Process the request.
byte[] resp = processRequest(buf, size);

//Write back the response to socket output stream.
os = socket.getOutputStream();
os.write(resp);
os.flush();
os.close();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(null != socket){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
minusCount();
}
}
}

//Subclasses to override it.
protected byte[] processRequest(byte[] buf, int size){

return buf;//echo it.

}
}

/**
* A mock server services to manage the mock servers.
*/
public class MockServerService {
private Set<MockServer> servers = null;

public void setServers(Set<MockServer> servers){
this.servers = servers;
}

protected void init(){
if(null != servers){
for(MockServer server: servers){
new Thread(server).start();
}
}
System.out.println("Service initialized.");
}

public Set<MockServer> getServers(){
return servers;
}

public void stop(){
if(null != servers){
for(Server server: servers){
server.stop();
}
}
System.out.println("Service stopped.");
}
}

Then we need an application entry point and integrate with Spring.

public class MockServer {
public static void main(String[] args){
AbstractApplicationContext ctxt = new ClassPathXmlApplicationContext("context-mockserver.xml");
ctxt.registerShutdownHook();
MockServerService service = (MockServerService) ctxt.getBean("mockServerService");
service.init();
}
}

the context file for Sring defining the beans.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="mockProcessor0" class="MockServer" >
<constructor-arg index="0" value="Server1"/>
<constructor-arg index="1" value="false"/>
<constructor-arg index="2" value="80"/>
<constructor-arg index="3" value="120000"/>
</bean>
<bean id="mockProcessor1" class="MockServer" >
<constructor-arg index="0" value="Server2"/>
<constructor-arg index="1" value="false"/>
<constructor-arg index="2" value="90"/>
<constructor-arg index="3" value="120000"/>
</bean>
<bean id="mockProcessor2" class="MockServer" >
<constructor-arg index="0" value="Server3"/>
<constructor-arg index="1" value="false"/>
<constructor-arg index="2" value="100"/>
<constructor-arg index="3" value="120000"/>
</bean>

<bean id="mockServerService" class="MockServerService">
<property name="servers">
<set>
<ref local="mockProcessor0"/>
<ref local="mockProcessor1"/>
<ref local="mockProcessor2"/>
</set>
</property>
</bean>
</beans>

Until now, nothing to do with JMX. You can run it as a normal Spring application. You can use JConsole to connect with it locally, if you run the application in this command line:

java -cp . -D-Dcom.sun.management.jmxremote MockServer

This is the snapshot of JConsole MBeans tab. Besides the default threads, memory etc. JVM MXBeans, you can't do anything else to this application.

Now, let's just simply tweak the context file, then see what happens. Add this extra block just at the end of the context file.

...

<bean id="mockServerService" class="MockServerService">
<property name="servers">
<set>
<ref local="mockProcessor0"/>
<ref local="mockProcessor1"/>
<ref local="mockProcessor2"/>
</set>
</property>
</bean>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=MockProcessor0" value-ref="mockProcessor0"/>
<entry key="bean:name=MockProcessor1" value-ref="mockProcessor1"/>
<entry key="bean:name=MockProcessor2" value-ref="mockProcessor2"/>
<entry key="bean:name=MockServerService" value-ref="mockServerService"/>
</map>
</property>
</bean>
...

Here it is! The new JConsole MBeans tab populated with your beans. Now you can see the name of each bean and stop it just by invoking the corresponding stop() method of that bean. Done! You can manage your Spring application now!






In a nutshell, following this pattern, you can tweak any of your applications to be manageable in minutes:

  1. Define the management interfaces for your Object.
  2. Spring your application and expose the object as MBean you want control. Nevertheless, Spring is a extremely noninvasive container, don't be afraid. The things need you Springlize is just make a context file for beans, add less than 5 lines of code to create the application context and put the spring.jar in your classpath. Everything is so familiar to you in a POJO world.
  3. Enable the JVM JMX platform MBean server in command line with -Dcom.sun.management.jmxremote and run it.
  4. Launch the JConsole and connect to the application then control it in your hands.

1 comment:

blogger said...

Great Blog!!! lot of intreseting stuff

You can also have a look on my blog at:
http://j2ee-now.blogspot.com/

Well well... why another J2EE blog? I benefited from other people's technical blogs, and guess what, it's a good idea to contribute some of my works too. Hope it's helpful and useful, to all of your folks.