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进程的。对于这一点在使用时必须要多注意。

Spring Bean的序列化方案

时间: 2024-08-26 01:57:55

Spring Bean的序列化方案的相关文章

【转】spring bean的生命周期

spring bean生命周期在传统的Java应用中,Bean的生命周期非常简单. Java的关键词new用来实例化Bean(或许他是非序列化的).这样就够用了. 相反,Bean的生命周期在Spring容器中更加细致. 理解Spring Bean的生命周期非常重要,因为你或许要利用Spring提供的机会来订制Bean的创建过程. 1.容器寻找Bean的定义信息并且将其实例化. 2.受用依赖注入,Spring按照Bean定义信息配置Bean的所有属性. 3.如果Bean实现了BeanNameAwa

Struts1.X与Spring集成——第二种方案

上篇博客介绍了Struts1.X与Spring集成的一种方案.Struts1.X与Spring集成--第一种方案 此篇博客还以上篇博客的登录例子为例,介绍Struts1.X与Spring集成的另一种方案. 1,第一种方案 原理 回忆第一种方案集成原理:在Action中取得BeanFactory,通过BeanFactory取得业务逻辑对象 此种方案的缺点:从严格意义的分层上来看,Action上看到了Spring的相关东西,依赖Spring API去查找东西,发生了依赖查找,因为要查找依赖对象,所以

Spring Bean的范围

当你配置一个bean的时候,你会通过bean的配置为实际的类实例创建一个配方(recipe ).bean的配置是一个配方的想法是很重要,因为它意味着,就像类一样,你可以通过一个配方来创建很多个实例. 你不仅可以控制被注入到由特定bean配置创建的对象的依赖和配置值,而且也可以控制对象的范围.这个方式是强大和灵活的,通过这种方式你可以通过配置文件来选择对象的范围,而不用放到Java类的级别.可以用多个范围内的一个来定义bean加载的范围:开箱即用,Spring支持5类范围,其中3个是只有你用到we

Spring - Bean Definition Inheritance

A bean definition can contain a lot of configuration information, including constructor arguments, property values, and container-specific information such as initialization method, static factory method name, and so on. A child bean definition inher

几种Android数据序列化方案

一.引言 数据的序列化在Android开发中占据着重要的地位,无论是在进程间通信.本地数据存储又或者是网络数据传输都离不开序列化的支持.而针对不同场景选择合适的序列化方案对于应用的性能有着极大的影响. 从广义上讲,数据序列化就是将数据结构或者是对象转换成我们可以存储或者传输的数据格式的一个过程,在序列化的过程中,数据结构或者对象将其状态信息写入到临时或者持久性的存储区中,而在对应的反序列化过程中,则可以说是生成的数据被还原成数据结构或对象的过程. 这样来说,数据序列化相当于是将我们原先的对象序列

Spring点滴四:Spring Bean生命周期

Spring Bean 生命周期示意图: 了解Spring的生命周期非常重要,我们可以利用Spring机制来定制Bean的实例化过程. --------------------------------------------------------------------------------------------------------------------------------------------------- spring-service.xml: <?xml version=

非spring组件servlet、filter、interceptor中注入spring bean

问题:在filter和interceptor中经常需要调用Spring的bean,filter也是配置在web.xml中的,请问一下这样调用的话,filter中调用Spring的某个bean,这个bean一定存在吗?现在总是担心filter调用bean的时候,bean还没被实例化? 答案:因为spring bean.filter.interceptor加载顺序与它们在 web.xml 文件中的先后顺序无关.即不会因为 filter 写在 listener 的前面而会先加载 filter.最终得出

Spring Bean

一.Spring的几大模块:Data access & Integration.Transcation.Instrumentation.Core Spring Container.Testing. 二.Spring Bean 2.1.声明Bean a.简单的bean装配方式 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework

AspectJ切面具体是什么时候实例化的?实在Spring Bean之前还是之后?

问题出于<Spring In Action>中4.5节 注入AspectJ切面时,看的不是太懂! 不懂得地方是:    AspectJ切面中需要注入Spring的Bean,那么AspectJ切面具体是什么时候实例化的?是在Spring Bean之前还是之后?