使用Spring Session做分布式会话管理

  在Web项目开发中,会话管理是一个很重要的部分,用于存储与用户相关的数据。通常是由符合session规范的容器来负责存储管理,也就是一旦容器关闭,重启会导致会话失效。因此打造一个高可用性的系统,必须将session管理从容器中独立出来。而这实现方案有很多种,下面简单介绍下:

  第一种是使用容器扩展来实现,大家比较容易接受的是通过容器插件来实现,比如基于Tomcat的tomcat-redis-session-manager,基于Jetty的jetty-session-redis等等。好处是对项目来说是透明的,无需改动代码。不过前者目前还不支持Tomcat 8,或者说不太完善。个人觉得由于过于依赖容器,一旦容器升级或者更换意味着又得从新来过。并且代码不在项目中,对开发者来说维护也是个问题。

  第二种是自己写一套会话管理的工具类,包括Session管理和Cookie管理,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中。很显然这个方案灵活性最大,但开发需要一些额外的时间。并且系统中存在两套Session方案,很容易弄错而导致取不到数据。

  第三种是使用框架的会话管理工具,也就是本文要说的spring-session,可以理解是替换了Servlet那一套会话管理,既不依赖容器,又不需要改动代码,并且是用了spring-data-redis那一套连接池,可以说是最完美的解决方案。当然,前提是项目要使用Spring Framework才行。

  这里简单记录下整合的过程:

  如果项目之前没有整合过spring-data-redis的话,这一步需要先做,在maven中添加这两个依赖:

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>1.5.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session</artifactId>
	<version>1.0.2.RELEASE</version>
</dependency>

  再在applicationContext.xml中添加以下bean,用于定义redis的连接池和初始化redis模版操作类,自行替换其中的相关变量。

<!-- redis -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
</bean>

<bean id="jedisConnectionFactory"
	class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
	<property name="hostName" value="${redis.host}" />
	<property name="port" value="${redis.port}" />
	<property name="password" value="${redis.pass}" />
	<property name="timeout" value="${redis.timeout}" />
	<property name="poolConfig" ref="jedisPoolConfig" />
	<property name="usePool" value="true" />
</bean>

<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
	<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>

<!-- 将session放入redis -->
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
	<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>

  这里前面几个bean都是操作redis时候使用的,最后一个bean才是spring-session需要用到的,其中的id可以不写或者保持不变,这也是一个约定优先配置的体现。这个bean中又会自动产生多个bean,用于相关操作,极大的简化了我们的配置项。其中有个比较重要的是springSessionRepositoryFilter,它将在下面的代理filter中被调用到。maxInactiveIntervalInSeconds表示超时时间,默认是1800秒。写上述配置的时候我个人习惯采用xml来定义,官方文档中有采用注解来声明一个配置类。

  然后是在web.xml中添加一个session代理filter,通过这个filter来包装Servlet的getSession()。需要注意的是这个filter需要放在所有filter链最前面

<!-- delegatingFilterProxy -->
<filter>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

  这样便配置完毕了,需要注意的是,spring-session要求Redis Server版本不低于2.8

  验证:使用redis-cli就可以查看到session key了,且浏览器Cookie中的jsessionid已经替换为session。

127.0.0.1:6379> KEYS *
1) "spring:session:expirations:1440922740000"
2) "spring:session:sessions:35b48cb4-62f8-440c-afac-9c7e3cfe98d3"

本文链接地址:https://dorole.com/1422/

来自:Dorole's Blog

发布者

Steve

编程/摄影

《使用Spring Session做分布式会话管理》上有34条评论

  1. 按照配置遇到如下报错,还未解决.
    严重: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
    org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Method ‘propertySourcesPlaceholderConfigurer’ must not be private, final or static; change the method’s modifiers to continue
    Offending resource: class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]
    at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
    at org.springframework.context.annotation.ConfigurationClassMethod.validate(ConfigurationClassMethod.java:61)
    at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:139)
    at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:167)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:198)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:142)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:599)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:407)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:276)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:197)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5099)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5615)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

  2. 用spring-session的时候如果再使用 session范围的bean 会爆不能序列化异常 遇到过吗?

  3. 我这里也出现了No bean named ‘springSessionRepositoryFilter’ is defined 可以发一个demo给我邮箱吗?万分感谢!!

        1. 我最後放棄使用 xml configuration 改用 java configuration, 亦就是說使用 annotation 的方式把 bean 注入, 已可正常使用
          JDK1.7
          Spring3.2.14

  4. 我也碰到了这个问题。错误如下:
    严重: Exception starting filter springSessionRepositoryFilter
    org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘springSessionRepositoryFilter’ is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:698)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1175)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1057)
    at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:326)
    at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:235)
    at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:199)
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:279)
    at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:260)
    at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:105)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4658)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5277)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1408)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1398)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

    六月 21, 2016 1:56:39 下午 org.apache.catalina.core.StandardContext startInternal
    严重: One or more Filters failed to start. Full details will be found in the appropriate container log file
    六月 21, 2016 1:56:39 下午 org.apache.catalina.core.StandardContext startInternal
    严重: Context [/springsession] startup failed due to previous errors

  5. 我也是报这个错误,郁闷:org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘springSessionRepositoryFilter’ is defined

    配置如下:
    web.xml

    contextConfigLocation
    /WEB-INF/applicationContext.xml

    springSessionRepositoryFilter
    org.springframework.web.filter.DelegatingFilterProxy

    springSessionRepositoryFilter
    /*

    org.springframework.web.context.ContextLoaderListener

    applicationContext.xml:

      1. java.lang.VerifyError: (class: org/springframework/data/redis/connection/jedis/JedisConnectionFactory, method: createRedisSentinelPool signature: (Lorg/springframework/data/redis/connection/RedisSentinelConfiguration;)Lredis/clients/util/Pool;) Incompatible argument to function
        我这个配置的是
        spring3.1.3 然后
        redis 2.1
        spring-session 1.0.2;
        spring-data-redis 1.5.2.RELEASE

  6. 按照你的配置后,我一启动 就报No bean named ‘springSessionRepositoryFilter’ is defined 全部都按照您的配置配置号的,也检查了好几遍。。还是希望你可以给我看看您的demo

    1. 我也遇到同样的问题,严重: Exception starting filter springSessionRepositoryFilter
      org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘springSessionRepositoryFilter’ is defined
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:387)

      1. 确认所有配置都没问题吗,还有名称。我这里是ok的,已经在生产环境跑了很久了。我这里Spring 版本是3.2的。调试下看有什么线索。

          1. 我取用sprint3.2.14 也是一樣的報錯
            RedisHttpSessionConfiguration看log是也有注入這個bean了 但還是沒取到filter bean..
            不知道該怎麼解決 現在挺苦惱的

              1. 大哥我的问题应该和你一样,我也是在集群环境下遇到的,我为了测试,配置了一主一从一哨兵,通过日志文件也能看出来确实都启动连接了
                信息: Trying to find master from available Sentinels…
                2016-8-18 10:01:33 redis.clients.jedis.JedisSentinelPool initSentinels
                信息: Redis master running at 127.0.0.1:6379, starting Sentinel listeners…
                2016-8-18 10:01:33 redis.clients.jedis.JedisSentinelPool initPool
                信息: Created JedisPool to master at 127.0.0.1:6379

                但是马上抛出异常
                org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘springSessionRepositoryFilter’ defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.springframework.session.SessionRepository]: : No qualifying bean of type [org.springframework.session.SessionRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.session.SessionRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
                求问您解决这个问题了吗

回复 黄豆 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: