如何动态实现spring 配置更新

关于Spring配置文件的动态载入的修改

     一        概述

Spring MVC 的开发是基于 action-servlet.xml 进行配置,但不支持开发模式下进行动态的配置文件载入。本文主要是介绍如何修改 Spring 的源代码,使 Spring 支持动态的配置文件更新,让开发变得更加简单。

二、        实现 action-servlet.xml 动态载入

    Spring 提取配置文件的思路 :每次 Spring MVC 会在使用前将 XML 文件载入内存中,并生成映射类的实例,放在 Mapping Map 里。然后判断每个请求,如果有其 URL 所对应的映射,则返回其对应的 Action 实例。

    修改思路 :将每次得到请求时,让程序重新载入 xml 文件,并实例化其映射,然后放入 Mapping Map 中。

1、             首先是 FrameworkServlet ,他是 DispatcherServlet 的基类。 XML 在载入内存后,放在一个叫 WebApplicationContext 的类中。找到 getWebApplicationContext() 方法,加入以下代码:

       ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils

              .instantiateClass(getContextClass());

       wac.setParent(WebApplicationContextUtils

              .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 文件一次。

 

2、             修改 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");

    }

三、实现 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" 是自定义的名称,可以任意修改。

 

3、             找到 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.

转载于:https://my.oschina.net/u/138995/blog/190776

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值