SpringBean的序列化⽅案
这个问题是在做beetl-spring扩展的时候遇到的⼀个问题。扩展的思想是尽可能允许Beetl模板⽤到的所有可配置组件都交给Spring容器管理。
springmvc选择题 但是遇到问题是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机制来实现,设计这样⼀个类,在应⽤程序上下⽂创建时,将他添加到缓存中,在应⽤程序上下⽂关闭时,将他从缓存中删除:
1package org.spring.utils;
2
3import java.util.HashMap;
4import java.util.Map;
5
6import org.apachemons.logging.Log;
7import org.apachemons.logging.LogFactory;
8import t.ApplicationContext;
9import t.ApplicationListener;
10import t.event.ApplicationContextEvent;
11import t.event.ContextClosedEvent;
12import t.event.ContextRefreshedEvent;
13import t.event.ContextStartedEvent;
14import t.event.ContextStoppedEvent;
15
16/**
17 * ApplicationContext⽣命周期事件
18 *
19 * @author Chen Rui
20*/
21public class ApplicationContextLifecycleEventListener implements ApplicationListener<ApplicationContextEvent> {
22private Log log = Log(ApplicationContextLifecycleEventListener.class);
23
24 @Override
25public void onApplicationEvent(ApplicationContextEvent event) {
26// 获取应⽤程序上下⽂
27 ApplicationContext applicationContext = ApplicationContext();
28 log.info(String.format("ApplicationContext⽣命周期事件(%s): %s",
29 applicationContext != null ? Id() : "null", Class().getSimpleName()));
30
31if (applicationContext != null) {
32if ((event instanceof ContextStoppedEvent) || (event instanceof ContextClosedEvent)) {
33// 应⽤程序上下⽂关闭或停⽌
34 Id());
35 } else if ((event instanceof ContextRefreshedEvent) || (event instanceof ContextStartedEvent)) {
36// 应⽤程序上下⽂启动或刷新
37 setApplicationContext(applicationContext);
38 }
39 }
40 }
41
42/* ----- ----- ----- ----- ApplicationContext管理 ----- ----- ----- ----- */
43/**
44 * application应⽤程序上下⽂
45*/
46private static Map<String, ApplicationContext> applicationContextPool = new HashMap<String, ApplicationContext>();
47
48/**
49 * 添加ApplicationContext对象
50 *
51 * @param applicationContext
52*/
53private static synchronized void setApplicationContext(ApplicationContext applicationContext) {
54 applicationContextPool.Id(), applicationContext);
55 }
56
57/**
58 * 删除ApplicationContext对象
59 *
60 * @param id
61*/
62private static synchronized void removeApplicationContext(String id) {
63 ve(id);
64 }
65
66/**
67 * 获取ID指定的ApplicationContext
68 *
69 * @param id
70 * @return
71*/
72public static synchronized ApplicationContext getApplicationContext(String id) {
(id);
74 }
75 }
将这个Bean定义在Spring容器中(Spring MVC项⽬中,只需要将他添加到顶层的WebApplicationContext中即可,对⼦上下⽂也会⽣效):
<bean class="org.spring.utils.ApplicationContextLifecycleEventListener"/>
第⼆个问题,我们要⼲涉SpringBeanTagFactory类的序列化机制,让他在序列化的时候只保存 ApplicationContext的标识和Bean名称,这⾥我们使⽤了Java序列化提供的writeReplace() 和 readResolve()⽅法。
⾸先定义⼀个实际⽤于序列化的类,他持有ApplicationContext的id值和bean名字进⾏实际的序列化,在反序列化时,通过readResolve()⽅法回实际被Spring Bean管理的Bean实例:
package org.spring.utils;
import java.io.Serializable;
import t.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));
}
Bean(beanName);
}
}
然后改写SpringBeanTagFactory类,让他能知道⾃⼰的Bean名和所在ApplicationContext的id(这通过 Spring容器感知特性很容易实现,只需要实现相应接⼝),然后提供writeReplace()⽅法,在序列化时,将实际执⾏序列化的对象替换成上⾯定义的SpringBeanSerializable对象:
package org.spring.tag;
import Tag;
import TagFactory;
import org.spring.utils.SpringBeanSerializable;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Required;
import t.ApplicationContext;
import t.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() {
Bean(name, Tag.class);
}
/* ----- ----- ----- ----- 序列化⽅法 ----- ----- ----- ----- */
/**
* ⽣成序列化替代类
*
* @return
*/
private Object writeReplace() {
return new Id(), beanName);
}
}
好了,到此⼤功告成。测试通过。
其实说⼤功告成还差得远,上⾯的解决⽅案有⼏个致命性的限制条件:
1. SpringBeanSerializable通过bean名从applicationContext中获取bean实例,所以SpringBeanTagFactory这个Bean必须是通过名字可直接获取的,即bean必须是公开的有明确名字的bean,不能是内部bean或匿名bean,否则反序列化时会抛出异常();
2. 通过如此反序列化得到的SpringBeanTagFactory,并不保证和原对象有相同的状态,即他实际是⽤ApplicationContext的标识和bean的名字来获取的,如果序列化内容传递到其他的JVM进程,实际反序列化时的 ApplicationContext即使标识相同,仍是两个⽆关的实例。即使ApplicationContext实例相同,如果bean本⾝ scope不保证单例的话,也可能造成⽆法完全还原序列化前bean的所有可变属性。对于这⼀点在使⽤时必须要多注意。
3.Web环境下的WebApplicationContext和DispacherServlet所持有的应⽤程序上下⽂的标识与进程⽆关。但在⼀般的Application应⽤程序中,ApplicationContext实例的id实际上是类名加hashcode() (如ClasspathXmlApplicationContext),在这种情况下,序列化数据不能传递出当前JVM进程的。对于这⼀点在使⽤时必须要多注意。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论