如何動態實現spring 配置更新

關於Spring配置文件的動態載入的修改 html

     一        概述 java

Spring MVC 的開發是基於 action-servlet.xml 進行配置,但不支持開發模式下進行動態的配置文件載入。本文主要是介紹如何修改 Spring 的源代碼,使 Spring 支持動態的配置文件更新,讓開發變得更加簡單。 ios

2、        實現 action-servlet.xml 動態載入 web

    Spring 提取配置文件的思路 :每次 Spring MVC 會在使用前將 XML 文件載入內存中,並生成映射類的實例,放在 Mapping Map 裏。而後判斷每一個請求,若是有其 URL 所對應的映射,則返回其對應的 Action 實例。 spring

    修改思路 :將每次獲得請求時,讓程序從新載入 xml 文件,並實例化其映射,而後放入 Mapping Map 中。 app

一、             首先是 FrameworkServlet ,他是 DispatcherServlet 的基類。 XML 在載入內存後,放在一個叫 WebApplicationContext 的類中。找到 getWebApplicationContext() 方法,加入如下代碼: dom

       ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils ide

              .instantiateClass(getContextClass()); flex

       wac.setParent(WebApplicationContextUtils ui

              .getWebApplicationContext(getServletContext()));

       wac.setServletContext(getServletContext());

       wac.setNamespace(getNamespace());

       if (getContextConfigLocation() != null ) {

           wac

                  .setConfigLocations(StringUtils

                         .tokenizeToStringArray(

                                getContextConfigLocation(),

                                ConfigurableWebApplicationContext. CONFIG_LOCATION_DELIMITERS ));

       }

       wac.refresh();

       this . webApplicationContext = wac;

這樣每次再讀取 WebApplicationContext 的時候,會從新載入 XML 文件一次。

 

二、             修改 DispatcherServlet ,這個 Servlet 是處理全部請求的入口。找到 getHandler() 這個方法,他負責找到相應的 Action ,並返回其實例。將代碼中的

       Iterator it = this.handlerMappings.iterator();

       while (it.hasNext()) {

           HandlerMapping hm = (HandlerMapping) it.next();

           if (logger.isDebugEnabled()) {

              logger.debug("Testing handler map [" + hm  + "] in DispatcherServlet with name '" +

                     getServletName() + "'");

           }

           handler = hm.getHandler(request);

           if (handler != null) {

              if (cache) {

                  request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);

              }

              return handler;

           }

       }

改成

       initHandlerMappings();

      

       Iterator it = this . handlerMappings .iterator();

       while (it.hasNext()) {

           BeanNameUrlHandlerMapping hm = (BeanNameUrlHandlerMapping) it.next();

           if ( logger .isDebugEnabled()) {

              logger .debug( "Testing handler map [" + hm  + "] in DispatcherServlet with name '" +

                     getServletName() + "'" );

           }

           hm.initApplicationContext();

           handler = hm.getHandler(request);

           if (handler != null ) {

              if (cache) {

                  request.setAttribute( HANDLER_EXECUTION_CHAIN_ATTRIBUTE , handler);

              }

              return handler;

           }

       }

註解:

1)   其中 BeanNameUrlHandlerMapping 是將強制轉換 HandlerMapping 時,用子類代替父類,由於子類提供了一個從新初始化的方法 initApplicationContext() ,調用該方法能夠從新載入 WebApplicationContext ,並刷新 Mapping Map 

2)       initHandlerMappings()  DispatcherServlet 初始化 Mapping 的一個方法。在生成 WebApplicationContext 時,程序還會把放在 ApplicationObjectSupport.applicationContext 保存,所以須要從新初始化一次。

 

3 、修改 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping

類中的 registerHandler() 方法,它的做用是註冊 Mapping ,去掉重複性校驗,將下面幾行代碼註釋掉。

    if (mappedHandler != null) {

        throw new ApplicationContextException(

               "Cannot map handler [" + handler + "] to URL path [" + urlPath +

               "]: there's already handler [" + mappedHandler + "] mapped");

    }

3、實現 applicationContext.xml 的動態載入

    Spring 實現思路: applicationContext.xml  Spring 默認的配置文件,它利用配置 ContextLoaderListener 的方式,在應用載入時啓動,並將 applicationContext.xml 載入內存中,放在 ServletContext  Attribute 中,保存的方式是一個 WebApplicationContext 類。當每次調用類時, beanFactory 會調用 WebApplicationContextUtils 中的方法 getWebApplicationContext() ,獲得配置信息。

    修改方法:  ContextLoaderListener 初始化 WebApplicationContext 時,會利用 ContextLoader 提供的方法 initWebApplicationContext() 進行初始化,咱們只須要獲得 Listener 的這個 ContextLoader 的實例,並從新調用一個初始化的方法就能夠實現從新載入了。

    修改步驟:

1 、找到 ContextLoaderListener 類的方法 contextInitialized() ,在 Context 初始化的時候將 ContextLoader 的引用放在 ServletContext  Attribute 中:

public void contextInitialized(ServletContextEvent event) {

       this . contextLoader = createContextLoader();

       this . contextLoader .initWebApplicationContext(event.getServletContext());

       event.getServletContext().setAttribute( "ListenerContextLoader" this . contextLoader );

}

注: "ListenerContextLoader" 是自定義的名稱,能夠任意修改。

 

三、             找到 WebApplicationContextUtils 類的方法 getWebApplicationContext() ,修改第一行代碼:

Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

改成:

       Object attr = null ;

       ContextLoader cl = (ContextLoader) sc

              .getAttribute( "ListenerContextLoader" );

       if (cl != null )

           attr = cl.initWebApplicationContext(sc);

這樣,在每次獲取 WebApplicationContext 時,程序會從新載入 applicationContext.xml 一次。

 

OK !大功告成, Enjoy your spring developing !!!


另一篇

http://techdive.in/spring/spring-refresh-application-context

In this section, we will discuss about how to reload an application context in Spring.

In many scenarios, you may require to reload the entire application context in Spring. But how to do it? There is actually an easy way to do. Have a look at the following code.

ApplicationContext ctx  =  newFileSystemXmlApplicationContext ( "Application-context.xml" ) ;
...
// Your application code here
...
( (ConfigurableApplicationContext )ctx ). refresh ( ) ;

Take a closer look at the last line, which type casts Application Context in to Configurable Application Context class part and then calls its refresh method. At this point in your application the entire application context (all the beans in Application-context.xml) will be reloaded or recreated.

This is the simple way to reload the context without restarting the web server.


再一篇

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.

http://hsenidians.blogspot.in/2007/07/reloading-spring-context-dynamically.html


Refresh/Reload Spring context dynamically

Spring is providing a method by which we can dynamically REFRESH/RELOAD entire context .
Create an ApplicationContextAware bean with following functions
/**
* setApplicationContext
*/
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
/**
* refreshContext()
*/
public void refreshContext(){
((ConfigurableApplicationContext)applicationContex t).refresh();
}
And call refreshContext() for the purpose.

相關文章
相關標籤/搜索