Saturday, July 14, 2007

Reloading the spring context dynamically

For those who have used spring framework as a standalone application, might encountered a difficulty in reloading the application context. It is easier for its web application context but not for the standalone.
What are the limitations in standalone spring server for reloading the context?
1) You do not have an built in API for doing it.
2) Since this is standalone, you need a RMI like stub to talk with the standalone application context.

So what are the solutions we have for dynamically reload the context.
1) You can frequently reload the context (Using trigger or quartz scheduler whatever), But this is not good since you may only need to reload on demand most of the times.
2) Then of course you have to implement a RMI based client to tell the server to reload it's context.

Since the item 1 is more straight forward, we will discuss the solution 2 today.

The easiest way to reload the context remotely on demand is JMX. The flexibility and simplicity of using JMX in spring make this very simple.
The idea is the, you have the platform mbean server for jdk1.5 , so you can simply export a bean as MBean. So it is just a matter of having a MonitorMBean for reloading the context and call that bean for reloading the server context.

This is my Monitor MBean interface

public interface MonitorMBean extends Serializable {
String reload();
}

This is the implementation for the interface

public class MonitorMBeanImpl implements MonitorMBean {

/**
* The MBean implementation for reloading method
*
* */
public String reload() {
//StandaloneSever is the class whic has the spring application context
StandaloneServer.reload();
return "Successfully reloaded the etl context";
}
}

Here come my context.xml for the server (I explain bean by bean, the complete source code is attached anyway)

First we will have the mbean server

<!-- Starting mbean server -->
<bean id="mbeanServer" class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer"/>

We have a POJO based bean called monitorBeanLocal.

<!-- monitor jmx mbean for the standalone server -->
<bean id="monitorBeanLocal" class="hsenidmobile.control.impl.MonitorMBeanImpl" depends-on="mbeanServer"/>

Now we expose our POJO to be a MBean

<!--Expose out monitor bean as jmx managed bean-->
<bean id="monitorMBean" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=monitorBean" value-ref="monitorBeanLocal"/>
</map>
</property>
<property name="server" ref="mbeanServer"/>
</bean>

Now lets have a RMI server connector

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1098"/>
</bean>

Of course we need the RMI registry also.

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean" depends-on="registry">
<property name="objectName" value="connector:name=rmi"/>
<property name="serviceUrl"
value="service:jmx:rmi://127.0.0.1/jndi/rmi://127.0.01:1098/server"/>
<property name="environment">
<props>
<prop key="jmx.remote.jndi.rebind">true</prop>
</props>
</property>
</bean>

Thats all about the JMX part. But for our testing purpose I have a bean called WhoAmI

<!-- Sample bean to see how this is reloaded -->
<bean id="whoAmI" class="hsenidmobile.control.domain.WhoAmI">
<property name="myName" value="JK"/>
</bean>


This bean is just a simple java bean additionally having a print method.
public class WhoAmI {
private String myName;

public void setMyName(String myName) {
this.myName = myName;
}

public void printMyName(){
System.out.println("My Name is now " + myName);
}
}
Cool, now lets go through our main server class.

public class StandaloneServer {
private static AbstractApplicationContext context;

public static void main(String[] args) {
if (args.length <>");
return;
}
String contextXml = args[0];
context = new FileSystemXmlApplicationContext(new String[]{contextXml}, true);
context.registerShutdownHook();//This will be useful incase if you want control the grace shutdown.

printMyName();
}

/**
* Method for reloading the context
* */
public static void reload() {
if (context == null) {
throw new RuntimeException("Context is not available");
}
System.out.println("Reloading the context");
context.refresh();
}

/**
*Test method for context reloading
* */
private static void printMyName() {
new Thread() {
public void run() {
while(true){
((WhoAmI) context.getBean("whoAmI")).printMyName();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//do nothing
}
}
}
}.start();
}
}
So we simply start the spring application there. Of course you can also see the simple method reload which is called by our monitor bean. The only different you would have noticed it, I use AbstractApplicationContext instead of ApplicationContext since it has the additional methods for our requirements.

Right we are done, Oh yes we need to test this, So how should we do. I give you a simple JMX client class to test this.

public class AdminClient {

public static void main(String[] args) {
String mbeanName = "bean:name=monitorBean";
MonitorMBean monitorMBean;
String serviceUrl = "service:jmx:rmi://localhost/jndi/rmi://localhost:1098/server";
try {
monitorMBean = createMbeanStub(mbeanName, serviceUrl);
monitorMBean.reload();
} catch (IOException e) {
System.out.println("IO Error occured while relading " + e); // Should use logger instead
} catch (MalformedObjectNameException e) {
System.out.println("Malformed error " + e); // Should use logger instead
}
System.out.println("The application context is reloaded successfully.");
}
}

private static MonitorMBean createMbeanStub(String mbeanName, String serviceUrl) throws MalformedObjectNameException,
IOException {
ObjectName mbeanObjectName = new ObjectName(mbeanName);
MBeanServerConnection serverConnection = connect(serviceUrl);
MonitorMBean monitorMBean;
monitorMBean = (MonitorMBean)MBeanServerInvocationHandler.newProxyInstance(serverConnection, mbeanObjectName,
MonitorMBean.class, false);
return monitorMBean;
}

private static MBeanServerConnection connect(String serviceUrl) throws IOException {
JMXServiceURL url = new JMXServiceURL(serviceUrl);
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
return jmxc.getMBeanServerConnection();
}
}

So here what we do is, we just invoke the monitor mbean's reload method to refresh the context.
Then So first you run the standalone
java hsenidmobile.control.StandaloneServer
You can see the output
My Name is now JK
My Name is now JK
My Name is now JK


Now you go and change the server.xml. Edit the whoAmI bean's name parameter from JK to CK. Then run our JMX client

java hsenidmobile.control.AdminClient
Now you can find the messages in the server console regarding to the reloading of the context. And also not the output message is changed to this

My Name is now CK
My Name is now CK
My Name is now CK
Cooool. Its simple as this.

So what we have done so far?

1) We are able to reload the spring standalone context remotely on demand. This enable us to change the server properties without restarting the server.

What we can do more?
1) If we have the properties in a database, or if you are willing to persist the properties in a file on the fly, then you can reload the context remotely by giving the arguments. You don't need to go and modify the server xml. (Thanks to JMX)

2) You have to be carefull about the singleton beans, since these beans will be destroyed and recreated for every reloading. So you may need to do the pre arrangement in the server before do the actual relaoding. But you will not need to worry about the non singleton beans. (There can be exceptional cases anyway).

3) You have to apply the AOP if possible. How about notifying the client application on reloading? You can do using spring AOP. I may put another blog on AOP soon. So stay tuned.

Ok we are done for today. Please find the attached codes for your reference.

BTW I used
jdk 1.5.0_11-b03 and
spring2.0.
The only dependencies are spring-2.0.jar and commons-logging-1.1.jar.

Click here to get the source codes for this sample.

2 comments:

JK said...

For some reason the formatting for the source code is not displayed properly in the post.But you can refer the attached codes for more understanding.

Unknown said...

Hi, i have a question.

Is it thread-safe to reload the context?

If there are multiple threads accessing the context during the reload what could happen?

Thanks!

Dvsantos