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

  • 相关阅读:
    HarmonyOS(鸿蒙OS)发布,聊聊操作系统的调度
    HarmonyOS(鸿蒙OS)发布,聊聊操作系统的调度
    修改SQL Server Management Studio 默认设置从而提高开发效率
    修改SQL Server Management Studio 默认设置从而提高开发效率
    还原默认的 SQL Server Management Studio 配置
    SQL2008智能提示失效
    SQL2008智能提示失效
    1028:字符菱形
    1028:字符菱形
    1027:输出浮点数
  • 原文地址:https://www.cnblogs.com/shishuifox/p/3852398.html
Copyright © 2020-2023  润新知