Spring-Session之动态启用

公司的系统在引入Spring-Session以实现Session共享之后,自然而然出现了动态启用的需求,毕竟在开发时我们并不需要开启这个功能。

1. 目标

以配置文件的形式,即通过一个配置项切换spring-session的启用和停止。

2. 分析

我们回顾下启用spring-session的必要条件:
1. 名为springSessionRepositoryFilterDelegatingFilterProxy注册到Servlet容器中。
2. 构造上面所代理的实际干活的Filter(spring-session-xx.jar中被@Configuration修饰的SpringHttpSessionConfiguration类构造的那个)时所必须的bean, 例如redis相关的JedisConnectionFactory等。

3. 解决方案

最终解决方案是
1. 使用Servlet3.0提供的ServletContainerInitializer接口功能来动态注册Filter
2. 使用Spring动态注入Bean的方式将Spring-Session必要的几个Bean注入到spring.xml生成的WebApplicationContext

4. 细节

4.1 动态注册DelegatingFilterProxy

参见本人的博客Spring-Session源码研究之Start_Servlet3.0 中的第1.3小节。因为注册这个Filter的逻辑其实Spring-Session都已经帮我们准备好了,我们所要做的只是进行一下简单的配置判断即可。为了方便,我这里还是贴一下相关代码。

```
public class SpringSessionInitializer extends AbstractHttpSessionApplicationInitializer {
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Servlet3.0+容器会回调本方法

        // 我们需要自定义决定是否载入这个Filter
        String isSingleUserLogin = (String) PropertiesUtil.getProperty("login.singleUserLogin");
        if (!("true".equalsIgnoreCase(isSingleUserLogin))) {
            return;
        }

        // 交给基类去注入Filter
        super.onStartup(servletContext);
    }
}
```
4.2 动态注册所必须的Bean

我们最终选择了通过实现BeanDefinitionRegistryPostProcessor接口来达到动态注册的目的。

这里有一点要注意的是解析@ConfigurationConfigurationClassPostProcessor, 也是实现了 BeanDefinitionRegistryPostProcessor接口。所以我们需要安排这两个类执行的先后优先级

我们注意到ConfigurationClassPostProcessor实现了PriorityOrdered这个标志性接口,其实现的getOrdered方法的返回值显示是最低优先级, 所以我们只需要比这个优先级稍微高一点就可以了。

// 通过实现BeanDefinitionRegistryPostProcessor接口来进行动态注册bean
// 实现PriorityOrdered接口将自己的被调用时机至于ConfigurationClassPostProcessor类的被调用时机前面; 
// 这样就可以在Spring框架解析 RedisHttpSessionConfiguration 配置类之前将其该配置内所需要的Bean(也就是本SingleUserBeanDefinitionRegistryPostProcessor类内部动态注册的Bean)注册进容器; 
// 这样在解析RedisHttpSessionConfiguration 配置类时就不会报告NoSuchBeanDefinitionException了.
public class SingleUserBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry bdRegistry) throws BeansException {
        String isSingleUserLogin = (String) PropertiesUtil.getProperty("login.singleUserLogin");
        if (!("true".equalsIgnoreCase(isSingleUserLogin))) {
            return;
        }

        // ------------------------- httpSessionStrategy
        // 最终会注入到SpringHttpSessionConfiguration中
        BeanDefinitionBuilder bdBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(CookieHttpSessionStrategyDecorator.class);
        //注册
        bdRegistry.registerBeanDefinition("httpSessionStrategy", bdBuilder.getRawBeanDefinition());

        // ------------------------- jedisConnectionFactory     
        bdBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(org.springframework.data.redis.connection.jedis.JedisConnectionFactory.class);

        //直接注入spel表达式; 注意这里是可以解析到的.
        // 具体的源码逻辑参见 AbstractBeanFactory 类的 evaluateBeanDefinitionString 方法
        // 如果想看点现成的, 可以参见《Spring源码深度解析》 P139

        //设置依赖
        bdBuilder.addPropertyValue("hostName", "${db.redis.hostName}");
        bdBuilder.addPropertyValue("port", "${db.redis.port}");

        //注册
    bdRegistry.registerBeanDefinition("redisConnectionFactory", bdBuilder.getRawBeanDefinition());

        // ------------------------- redisHttpSessionConfiguration
        //构造bean定义
        bdBuilder = BeanDefinitionBuilder.genericBeanDefinition(RedisHttpSessionConfiguration.class);

        //设置依赖
        bdBuilder.addPropertyValue("maxInactiveIntervalInSeconds",
                "${db.redis.maxInactiveIntervalInSeconds}");

        //注册
        bdRegistry.registerBeanDefinition("redisHttpSessionConfiguration", bdBuilder
                .getRawBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {

    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE + 1; // 比ConfigurationClassPostProcessor 级别高一些
    }

}

5. 后记

  1. 当想到需要动态注册时,本人的第一反应就是BeanFactoryPostProcessor
  2. 然后在网上找了下, 发现还可以使用ImportBeanDefinitionRegistrarBeanDefinitionRegistryPostProcessor。后面这两看名字大概就能猜到做啥用的,所以Spring里的命名还是相当不错的!
  3. 关于上面这两个接口可以直接看下面的博文。

6. 参考

  1. 动态注册bean,Spring官方套路:使用ImportBeanDefinitionRegistrar
  2. 动态注册bean,Spring官方套路:使用BeanDefinitionRegistryPostProcessor
  3. spring中注册bean(通过代码动态注册)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值