Spring Bean的序列化方案


  這個問題是在做beetl-spring擴展的時候遇到的一個問題。擴展的思想是盡可能允許Beetl模板用到的所有可配置組件都交給Spring容器管理。

  但是遇到問題是Beetl引擎在內部對模板執行進行優化的時候有使用Java對象序列化和反序列化來實現深拷貝,序列化的對象中包括了一個 可能被Spring管理的Bean:SpringBeanTagFactory,通過這個操作該對象會從Spring容器中脫管,更麻煩的該對象是通過 ApplicationContextAware注入的ApplicationContext實例無法序列化。

  在這樣的基礎上,想到如下的解決方案:SpringBeanTagFactory序列化時,只序列化當前Bean的名字和Bean所在的 ApplicationContext的標識(id),反序列化時通過ApplicationContext標識找到對應的 ApplicationContext實例,再繼續通過Bean名獲取到對應的實例。


  首先的第一個問題,我們應該維護好ApplicationContext id與ApplicationContext實例的關系,這在Spring MVC項目中很重要(因為除了頂層的WebApplicationContext外,每個DispatcherServlet都對應了一個子 ApplicationContext),這個維護工作可以采用Spring ApplicationEvent機制來實現,設計這樣一個類,在應用程序上下文創建時,將他添加到緩存中,在應用程序上下文關閉時,將他從緩存中刪除:

 1 package org.fox.beetl.ext.spring.utils;
 2  
 3 import java.util.HashMap;
 4 import java.util.Map;
 5  
 6 import org.apache.commons.logging.Log;
 7 import org.apache.commons.logging.LogFactory;
 8 import org.springframework.context.ApplicationContext;
 9 import org.springframework.context.ApplicationListener;
10 import org.springframework.context.event.ApplicationContextEvent;
11 import org.springframework.context.event.ContextClosedEvent;
12 import org.springframework.context.event.ContextRefreshedEvent;
13 import org.springframework.context.event.ContextStartedEvent;
14 import org.springframework.context.event.ContextStoppedEvent;
15  
16 /**
17  * ApplicationContext生命周期事件監聽器
18  * 
19  * @author Chen Rui
20  */
21 public class ApplicationContextLifecycleEventListener implements ApplicationListener<ApplicationContextEvent> {
22     private Log log = LogFactory.getLog(ApplicationContextLifecycleEventListener.class);
23  
24     @Override
25     public void onApplicationEvent(ApplicationContextEvent event) {
26         // 獲取應用程序上下文
27         ApplicationContext applicationContext = event.getApplicationContext();
28         log.info(String.format("ApplicationContext生命周期事件(%s): %s",
29                 applicationContext != null ? applicationContext.getId() : "null", event.getClass().getSimpleName()));
30  
31         if (applicationContext != null) {
32             if ((event instanceof ContextStoppedEvent) || (event instanceof ContextClosedEvent)) {
33                 // 應用程序上下文關閉或停止
34                 removeApplicationContext(applicationContext.getId());
35             } else if ((event instanceof ContextRefreshedEvent) || (event instanceof ContextStartedEvent)) {
36                 // 應用程序上下文啟動或刷新
37                 setApplicationContext(applicationContext);
38             }
39         }
40     }
41  
42     /* ----- ----- ----- ----- ApplicationContext管理 ----- ----- ----- ----- */
43     /**
44      * application應用程序上下文
45      */
46     private static Map<String, ApplicationContext> applicationContextPool = new HashMap<String, ApplicationContext>();
47  
48     /**
49      * 添加ApplicationContext對象
50      * 
51      * @param applicationContext
52      */
53     private static synchronized void setApplicationContext(ApplicationContext applicationContext) {
54         applicationContextPool.put(applicationContext.getId(), applicationContext);
55     }
56  
57     /**
58      * 刪除ApplicationContext對象
59      * 
60      * @param id
61      */
62     private static synchronized void removeApplicationContext(String id) {
63         applicationContextPool.remove(id);
64     }
65  
66     /**
67      * 獲取ID指定的ApplicationContext
68      * 
69      * @param id
70      * @return
71      */
72     public static synchronized ApplicationContext getApplicationContext(String id) {
73         return applicationContextPool.get(id);
74     }
75 }

  將這個Bean定義在Spring容器中(Spring MVC項目中,只需要將他添加到頂層的WebApplicationContext中即可,對子上下文也會生效):

<bean class="org.fox.beetl.ext.spring.utils.ApplicationContextLifecycleEventListener"/>

  第二個問題,我們要干涉SpringBeanTagFactory類的序列化機制,讓他在序列化的時候只保存 ApplicationContext的標識和Bean名稱,這里我們使用了Java序列化提供的writeReplace() 和 readResolve()方法。

  首先定義一個實際用於序列化的類,他持有ApplicationContext的id值和bean名字進行實際的序列化,在反序列化時,通過readResolve()方法找回實際被Spring Bean管理的Bean實例:

package org.fox.beetl.ext.spring.utils;
 
import java.io.Serializable;
 
import org.springframework.context.ApplicationContext;
 
/**
 * Spring Bean序列化類
 * 
 * @author Chen Rui
 */
public class SpringBeanSerializable implements Serializable {
    private static final long serialVersionUID = 1L;
 
    /**
     * Bean所屬applicationContextId
     */
    private String applicationContextId = null;
    /**
     * bean名稱
     */
    private String beanName = null;
 
    /**
     * @param applicationContextId
     * @param beanName
     */
    public SpringBeanSerializable(String applicationContextId, String beanName) {
        this.applicationContextId = applicationContextId;
        this.beanName = beanName;
    }
 
    /**
     * 將序列化內容還原成原Spring Bean的方法
     * 
     * @return
     */
    private Object readResolve() {
        ApplicationContext applicationContext = ApplicationContextLifecycleEventListener
                .getApplicationContext(applicationContextId);
 
        if (applicationContext == null) {
            throw new IllegalStateException(String.format("id為%s的ApplicationContext不存在", applicationContextId));
        }
 
        return applicationContext.getBean(beanName);
    }
}

  然后改寫SpringBeanTagFactory類,讓他能知道自己的Bean名和所在ApplicationContext的id(這通過 Spring容器感知特性很容易實現,只需要實現相應接口),然后提供writeReplace()方法,在序列化時,將實際執行序列化的對象替換成上面 定義的SpringBeanSerializable對象:

package org.fox.beetl.ext.spring.tag;
 
import org.beetl.core.Tag;
import org.beetl.core.TagFactory;
import org.fox.beetl.ext.spring.utils.SpringBeanSerializable;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
 
/**
 * 使用指定名字的Spring Bean為Beetl的Tag對象 注意這個Tag Bean應該是prototype而非單例的,否則在程序中會有問題
 * 
 * @author Chen Rui
 */
public class SpringBeanTagFactory implements TagFactory, ApplicationContextAware, BeanNameAware {
    private static final long serialVersionUID = 1L;
 
    /* ----- ----- ----- ----- 屬性 ----- ----- ----- ----- */
    /**
     * 目標Bean名
     */
    private String name = null;
    /**
     * Spring 應用程序上下文
     */
    private ApplicationContext applicationContext = null;
    /**
     * Spring Bean名稱
     */
    private String beanName = null;
 
    /**
     * 目標Bean名
     * 
     * @param name
     */
    @Required
    public void setName(String name) {
        this.name = name;
    }
 
    /**
     * Spring 應用程序上下文
     * 
     * @param applicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
 
    /**
     * Spring Bean名稱
     * 
     * @param beanName
     */
    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
 
    /* ----- ----- ----- ----- 其他方法 ----- ----- ----- ----- */
    /**
     * 返回上下文中對應Tag bean對象
     * 
     * @return
     */
    @Override
    public Tag createTag() {
        return applicationContext.getBean(name, Tag.class);
    }
 
    /* ----- ----- ----- ----- 序列化方法 ----- ----- ----- ----- */
    /**
     * 生成序列化替代類
     * 
     * @return
     */
    private Object writeReplace() {
        return new SpringBeanSerializable(applicationContext.getId(), beanName);
    }
}

  好了,到此大功告成。測試通過。


  其實說大功告成還差得遠,上面的解決方案有幾個致命性的限制條件:

  1. SpringBeanSerializable通過bean名從applicationContext中獲取bean實例,所以SpringBeanTagFactory這個Bean必須是通過名字可直接獲取的,即bean必須是公開的有明確名字的bean,不能是內部bean或匿名bean,否則反序列化時會拋出異常(NoSuchBean......);

  2. 通過如此反序列化得到的SpringBeanTagFactory,並不保證和原對象有相同的狀態, 即他實際是用ApplicationContext的標識和bean的名字來獲取的,如果序列化內容傳遞到其他的JVM進程,實際反序列化時的 ApplicationContext即使標識相同,仍是兩個無關的實例。即使ApplicationContext實例相同,如果bean本身 scope不保證單例的話,也可能造成無法完全還原序列化前bean的所有可變屬性。對於這一點在使用時必須要多注意。

  3.Web環境下的WebApplicationContext和DispacherServlet所持有的應用程序上下文的標識與進程無關。但在一般的Application應用程序中,ApplicationContext實例的id實際上是類名加hashcode() (如ClasspathXmlApplicationContext),在這種情況下,序列化數據不能傳遞出當前JVM進程的。對於這一點在使用時必須要多注意。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM