`
bugyun
  • 浏览: 557360 次
社区版块
存档分类
最新评论

利用spring session解决共享Session问题(转)

 
阅读更多

 

转自:http://blog.csdn.net/patrickyoung6625/article/details/45694157

 

1.共享Session问题

 

HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的。而如果我们把web服务器搭建成分布式的集群,然后利用LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到两个不同的web站点中去。那么问题就来了,如何保证不同的web站点能够共享同一份session数据呢?

 
最简单的想法就是把session数据保存到内存以外的一个统一的地方,例如Memcached/Redis等数据库中。那么问题又来了,如何替换掉Servlet容器创建和管理HttpSession的实现呢?
 
(1)设计一个Filter,利用HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作。spring-session就是通过这样的思路实现的。
(2)利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。不过这种方式有个缺点,就是需要耦合Tomcat/Jetty等Servlet容器的代码。这方面其实早就有开源项目了,例如memcached-session-manager,以及tomcat-redis-session-manager。暂时都只支持Tomcat6/Tomcat7。
 

2.Spring Session介绍

 

Spring Sessionspring的项目之一,GitHub地址:https://github.com/spring-projects/spring-session。

Spring Session提供了一套创建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。

 

 

 

下面是来自官网的特性介绍:

 

Features

 

Spring Session provides the following features:

  • API and implementations for managing a user's session
  • HttpSession - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way
    • Clustered Sessions - Spring Session makes it trivial to support clustered sessions without being tied to an application container specific solution.
    • Multiple Browser Sessions - Spring Session supports managing multiple users' sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google).
    • RESTful APIs - Spring Session allows providing session ids in headers to work with RESTful APIs
  • WebSocket - provides the ability to keep the HttpSession alive when receiving WebSocket messages
 

3.集成Spring Session的正确姿势

 

下面是实际调试通过的例子,包含下面4个步骤:

(1)第一步,添加Maven依赖

 

根据官网Quick Start展示的依赖,在项目pom.xml中添加后各种找不到类引用。于是查看Spring Session项目的build.gradle文件,居然没有配置依赖的项目,难道还要我自己去找它的依赖,太不专业了吧?!!!

 

[html] view plain copy
 
  1. <dependencies>  
  2.     <dependency>  
  3.         <groupId>org.springframework.session</groupId>  
  4.         <artifactId>spring-session</artifactId>  
  5.         <version>1.0.1.RELEASE</version>  
  6.     </dependency>  
  7. </dependencies>  
 

终于在多番仔细研究Spring Session项目源码之后,看到了spring-session-data-redis项目:

 

build.gradle文件里配置了Spring Session编译依赖的3个项目:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. apply from: JAVA_GRADLE  
  2. apply from: MAVEN_GRADLE  
  3.   
  4. apply plugin: 'spring-io'  
  5.   
  6. description = "Aggregator for Spring Session and Spring Data Redis"  
  7.   
  8. dependencies {  
  9.     compile project(':spring-session'),  
  10.             "org.springframework.data:spring-data-redis:$springDataRedisVersion",  
  11.             "redis.clients:jedis:$jedisVersion",  
  12.             "org.apache.commons:commons-pool2:$commonsPoolVersion"  
  13.   
  14.     springIoVersions "io.spring.platform:platform-versions:${springIoVersion}@properties"  
  15. }  

于是,真正的Maven依赖改成spring-session-data-redis就OK了:

 

[html] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. <dependency>  
  2.     <groupId>org.springframework.session</groupId>  
  3.     <artifactId>spring-session-data-redis</artifactId>  
  4.     <version>1.0.1.RELEASE</version>  
  5. </dependency>  

(2)第二步,编写一个配置类,用来启用RedisHttpSession功能,并向Spring容器中注册一个RedisConnectionFactory。

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. import org.springframework.context.annotation.Bean;  
  2. import org.springframework.data.redis.connection.RedisConnectionFactory;  
  3. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;  
  4. import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;  
  5.   
  6. @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200)  
  7. public class RedisHttpSessionConfig {  
  8.   
  9.     @Bean  
  10.     public RedisConnectionFactory connectionFactory() {  
  11.         JedisConnectionFactory connectionFactory = new JedisConnectionFactory();  
  12.         connectionFactory.setPort(6379);  
  13.         connectionFactory.setHostName("10.18.15.190");  
  14.         return connectionFactory;  
  15.     }  
  16. }  

(3)第三步,将RedisHttpSessionConfig加入到WebInitializer#getRootConfigClasses()中,让Spring容器加载RedisHttpSessionConfig类。WebInitializer是一个自定义的AbstractAnnotationConfigDispatcherServletInitializer实现类,该类会在Servlet启动时加载(当然也可以采用别的加载方法,比如采用扫描@Configuration注解类的方式等等)。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. //该类采用Java Configuration,来代替web.xml     
  2. public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {  
  3.       
  4.     @Override  
  5.     protected Class<?>[] getRootConfigClasses() {  
  6.         return new Class[]{Config1.class, Config2.class, RedisHttpSessionConfig.class};  
  7.     }  
  8.       
  9.     //......  
  10. }  

 

(4)第四步,编写一个一个AbstractHttpSessionApplicationInitializer实现类,用于向Servlet容器中添加springSessionRepositoryFilter。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;  
  2.   
  3. public class SpringSessionInitializer extends AbstractHttpSessionApplicationInitializer {  
  4. }  

 

4. Spring Session原理

 

(1)前面集成spring-sesion的第二步中,编写了一个配置类RedisHttpSessionConfig,它包含注解@EnableRedisHttpSession,并通过@Bean注解注册了一个RedisConnectionFactory到Spring容器中。

而@EnableRedisHttpSession注解通过Import,引入了RedisHttpSessionConfiguration配置类。该配置类通过@Bean注解,向Spring容器中注册了一个SessionRepositoryFilterSessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)。

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package org.springframework.session.data.redis.config.annotation.web.http;  
  2.   
  3. @Configuration  
  4. @EnableScheduling  
  5. public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoaderAware {  
  6.     //......  
  7.       
  8.     @Bean  
  9.     public RedisTemplate<String,ExpiringSession> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {  
  10.         //......  
  11.         return template;  
  12.     }  
  13.       
  14.     @Bean  
  15.     public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, ExpiringSession> sessionRedisTemplate) {  
  16.         //......  
  17.         return sessionRepository;  
  18.     }  
  19.       
  20.     @Bean  
  21.     public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository, ServletContext servletContext) {  
  22.         //......  
  23.         return sessionRepositoryFilter;  
  24.     }  
  25.       
  26.     //......  
  27. }  

 

(2)集成spring-sesion的第四步中,我们编写了一个SpringSessionInitializer 类,它继承自AbstractHttpSessionApplicationInitializer。该类不需要重载或实现任何方法,它的作用是在Servlet容器初始化时,从Spring容器中获取一个默认名叫sessionRepositoryFilter的过滤器类(之前没有注册的话这里找不到会报错),并添加到Servlet过滤器链中。

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package org.springframework.session.web.context;  
  2.   
  3. /** 
  4.  * Registers the {@link DelegatingFilterProxy} to use the 
  5.  * springSessionRepositoryFilter before any other registered {@link Filter}.  
  6.  * 
  7.  * ...... 
  8.  */  
  9. @Order(100)  
  10. public abstract class AbstractHttpSessionApplicationInitializer implements WebApplicationInitializer {  
  11.   
  12.     private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";  
  13.   
  14.     public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter";  
  15.   
  16.     //......  
  17.   
  18.     public void onStartup(ServletContext servletContext)  
  19.             throws ServletException {  
  20.         beforeSessionRepositoryFilter(servletContext);  
  21.         if(configurationClasses != null) {  
  22.             AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();  
  23.             rootAppContext.register(configurationClasses);  
  24.             servletContext.addListener(new ContextLoaderListener(rootAppContext));  
  25.         }  
  26.         insertSessionRepositoryFilter(servletContext);//注册一个SessionRepositoryFilter  
  27.         afterSessionRepositoryFilter(servletContext);  
  28.     }  
  29.   
  30.     /** 
  31.      * Registers the springSessionRepositoryFilter 
  32.      * @param servletContext the {@link ServletContext} 
  33.      */  
  34.     private void insertSessionRepositoryFilter(ServletContext servletContext) {  
  35.         String filterName = DEFAULT_FILTER_NAME;//默认名字是springSessionRepositoryFilter  
  36.         DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);//该Filter代理会在初始化时从Spring容器中查找springSessionRepositoryFilter,之后实际会使用SessionRepositoryFilter进行doFilter操作         
  37.         String contextAttribute = getWebApplicationContextAttribute();  
  38.         if(contextAttribute != null) {  
  39.             springSessionRepositoryFilter.setContextAttribute(contextAttribute);  
  40.         }  
  41.         registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);  
  42.     }  
  43.       
  44.     //......  
  45. }  

SessionRepositoryFilter是一个优先级最高的javax.servlet.Filter,它使用了一个SessionRepositoryRequestWrapper类接管了Http Session的创建和管理工作。

 

注意下面给出的是简化过的示例代码,与spring-session项目的源代码有所差异。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @Order(SessionRepositoryFilter.DEFAULT_ORDER)  
  2. public class SessionRepositoryFilter implements Filter {  
  3.   
  4.         public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {  
  5.                 HttpServletRequest httpRequest = (HttpServletRequest) request;  
  6.                 SessionRepositoryRequestWrapper customRequest =  
  7.                         new SessionRepositoryRequestWrapper(httpRequest);  
  8.   
  9.                 chain.doFilter(customRequest, response, chain);  
  10.         }  
  11.   
  12.         // ...  
  13. }  

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {  
  2.   
  3.         public SessionRepositoryRequestWrapper(HttpServletRequest original) {  
  4.                 super(original);  
  5.         }  
  6.   
  7.         public HttpSession getSession() {  
  8.                 return getSession(true);  
  9.         }  
  10.   
  11.         public HttpSession getSession(boolean createNew) {  
  12.                 // create an HttpSession implementation from Spring Session  
  13.         }  
  14.   
  15.         // ... other methods delegate to the original HttpServletRequest ...  
  16. }  
(3)好了,剩下的问题就是,如何在Servlet容器启动时,加载下面两个类。幸运的是,这两个类由于都实现了WebApplicationInitializer接口,会被自动加载
  • WebInitializer,负责加载配置类。它继承自AbstractAnnotationConfigDispatcherServletInitializer,实现了WebApplicationInitializer接口
  • SpringSessionInitializer,负责添加sessionRepositoryFilter的过滤器类。它继承自AbstractHttpSessionApplicationInitializer,实现了WebApplicationInitializer接口

 

在Servlet3.0规范中,Servlet容器启动时会自动扫描javax.servlet.ServletContainerInitializer的实现类,在实现类中我们可以定制需要加载的类。在spring-web项目中,有一个ServletContainerInitializer实现类SpringServletContainerInitializer,它通过注解@HandlesTypes(WebApplicationInitializer.class),让Servlet容器在启动该类时,会自动寻找所有的WebApplicationInitializer实现类。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package org.springframework.web;  
  2.   
  3. @HandlesTypes(WebApplicationInitializer.class)  
  4. public class SpringServletContainerInitializer implements ServletContainerInitializer {  
  5.   
  6.     /** 
  7.      * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} 
  8.      * implementations present on the application classpath. 
  9.      * 
  10.      * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, 
  11.      * Servlet 3.0+ containers will automatically scan the classpath for implementations 
  12.      * of Spring's {@code WebApplicationInitializer} interface and provide the set of all 
  13.      * such types to the {@code webAppInitializerClasses} parameter of this method. 
  14.      * 
  15.      * <p>If no {@code WebApplicationInitializer} implementations are found on the 
  16.      * classpath, this method is effectively a no-op. An INFO-level log message will be 
  17.      * issued notifying the user that the {@code ServletContainerInitializer} has indeed 
  18.      * been invoked but that no {@code WebApplicationInitializer} implementations were 
  19.      * found. 
  20.      * 
  21.      * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected, 
  22.      * they will be instantiated (and <em>sorted</em> if the @{@link 
  23.      * org.springframework.core.annotation.Order @Order} annotation is present or 
  24.      * the {@link org.springframework.core.Ordered Ordered} interface has been 
  25.      * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)} 
  26.      * method will be invoked on each instance, delegating the {@code ServletContext} such 
  27.      * that each instance may register and configure servlets such as Spring's 
  28.      * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener}, 
  29.      * or any other Servlet API componentry such as filters. 
  30.      * 
  31.      * @param webAppInitializerClasses all implementations of 
  32.      * {@link WebApplicationInitializer} found on the application classpath 
  33.      * @param servletContext the servlet context to be initialized 
  34.      * @see WebApplicationInitializer#onStartup(ServletContext) 
  35.      * @see AnnotationAwareOrderComparator 
  36.      */  
  37.     @Override  
  38.     public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)  
  39.             throws ServletException {  
  40.         //......  
  41.     }  
  42.   
  43. }  

 

5. 如何在Redis中查看Session数据?

 

 

(1)Http Session数据在Redis中是以Hash结构存储的。

 
(2)可以看到,还有一个key="spring:session:expirations:1431577740000"的数据,是以Set结构保存的。这个值记录了所有session数据应该被删除的时间(即最新的一个session数据过期的时间)。
[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 127.0.0.1:6379> keys *  
  2. 1) "spring:session:expirations:1431577740000"  
  3. 2) "spring:session:sessions:e2cef3ae-c8ea-4346-ba6b-9b3b26eee578"  
  4. 127.0.0.1:6379> type spring:session:sessions:e2cef3ae-c8ea-4346-ba6b-9b3b26eee578  
  5. hash  
  6. 127.0.0.1:6379> type spring:session:expirations:1431577740000  
  7. set  

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 127.0.0.1:6379> keys *  
  2. 1) "spring:session:expirations:1431527520000"  
  3. 2) "spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b"  
  4. 3) "spring:session:sessions:11a69da6-138b-42bc-9916-60ae78aa55aa"  
  5. 4) "spring:session:sessions:0a51e2c2-4a3b-4986-a754-d886d8a5d42d"  
  6. 5) "spring:session:expirations:1431527460000"  
  7.   
  8. 127.0.0.1:6379> hkeys spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b  
  9. 1) "maxInactiveInterval"  
  10. 2) "creationTime"  
  11. 3) "lastAccessedTime"  
  12. 4) "sessionAttr:attr1"  
  13.   
  14. 127.0.0.1:6379> hget spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b sessionAttr:attr1  
  15. "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x03"  
  16.   
  17. 127.0.0.1:6379> hget spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b creationTime  
  18. "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01MM\x94(\xec"  
 

6.参考文章

 

Spring Session 1.01 Reference

spring session入门

集群session共享机制

 

 
 
分享到:
评论

相关推荐

    Spring Session + redis实现session共享

    Spring Session + Redis的结合提供了一个高效且可靠的解决方案,它允许跨服务器节点透明地共享session数据。本文将深入探讨如何利用Spring Session与Redis来实现这一功能。 首先,Spring Session是一个开源项目,由...

    spring session实现session共享

    Spring Session的出现,就是为了克服这个问题,它支持将Session数据持久化到各种后端存储,如Redis、MongoDB、Hazelcast等,从而实现跨服务器的Session共享。 **Spring Session核心概念** 1. **Session Registry**...

    Redis、springSession共享包

    为了解决这个问题,我们可以利用 Redis 的分布式特性将 Session 存储在 Redis 中,SpringSession 提供了与原生 Session 接口一致的抽象,使得集成变得简单。 集成步骤如下: 1. **添加依赖**:在项目的构建配置...

    解决springboot实现跨域session共享问题

    本文将详细讲解如何在Spring Boot应用中解决跨域session共享的问题,并探讨防止SQL注入的相关策略。 首先,让我们理解什么是跨域。跨域是指由于浏览器的同源策略限制,不同域名、协议或端口之间的页面无法直接通信...

    Spring boot集成spring session实现session共享的方法

    Spring Boot 集成 Spring Session 实现 Session 共享的方法 Spring Boot 是目前流行的 Java 框架,它提供了许多便捷的功能和配置项,使得开发者可以快速构建项目。但是,在分布式环境中,Session 的共享变得非常...

    spring session redis分布式session

    为了解决这个问题,Spring Session提供了一种优雅的解决方案,特别是结合Redis作为持久化存储时,可以实现高效的分布式Session管理。本文将详细介绍Spring Session与Redis集成,以及如何自定义Session来应对分布式...

    SpringSession+Redis实现Session共享案例

    SpringSession结合Redis实现Session共享是Web开发中一种常见的解决方案,特别是在分布式系统中,为了保持用户在不同服务器之间访问时的会话一致性。本案例旨在教你如何配置和使用SpringSession与Redis来达到这一目的...

    SpringSession+redis共享jar包

    SpringSession通过将Session数据持久化到外部存储,如Redis,实现了跨服务器的Session共享。 Redis 是一个高性能的键值数据库,常被用作缓存和消息代理。在SpringSession中,Redis被用作Session的存储后端,因为其...

    学习Spring-Session+Redis实现session共享

    为了实现这一目标,就需要解决不同服务器间Session共享的问题。Spring-Session正是为此而设计的一个框架,它提供了一种机制来存储用户的会话信息至中央数据存储区,如Redis等,从而实现跨服务共享。 #### 一、...

    SpringBoot+MyBatis+SpringSession+Redis实现session共享及单点登录

    SpringBoot+MyBatis+SpringSession+Redis实现session共享及单点登录开发实例

    springsession管理多台tomcatsession

    为了解决这个问题,可以采用 SpringSession 结合 Redis,因为 Redis 是一个高效的内存数据结构存储系统,可以作为共享 session 存储,确保多台服务器间 session 数据的一致性。 接下来,我们详细介绍实现步骤: 1....

    spring redis session共享实现步骤

    总的来说,Spring Session与Redis的结合提供了一种高效且可扩展的Session共享解决方案,尤其适合大型分布式系统的应用场景。通过合理配置和实践,你可以轻松地在不同的Web服务器之间共享用户的Session数据。

    SpringSession同时支持Cookie和header策略

    SpringSession提供了解决这些问题的方案,它支持通过HTTP头部来传递session信息,这称为header策略。 Cookie策略是基于HTTP Cookie标准,当用户访问服务器时,服务器会发送一个Set-Cookie响应头,包含session ID。...

    Spring中自定义session管理,SpringSession的使用

    SpringSession的引入解决了这个问题,它提供了一种在多个应用服务器之间共享Session数据的优雅方式。本文将详细介绍如何在Spring项目中自定义Session管理以及如何使用SpringSession。 一、SpringSession简介 Spring...

    nginx+spring-session+redis 实现session共享

    "nginx+spring-session+redis 实现session共享"是分布式系统中常见的解决方案,它结合了Nginx的负载均衡能力、Spring-Session的数据持久化特性以及Redis的高可用性,有效地解决了分布式环境下的会话管理问题。...

    基于spring redis的shiro session共享

    所以系统一旦引入shiro后,采用传统的tomcat session共享机制是无效的,必须采用面向shiro 的session共享。 网上针对“shiro session共享”的文章比较多,但是大同小异,基本是基于redis实现的。但是该套实现,代码...

    Springboot+SpringSecurity+SpringSession+Redis+Mybatis-Plus+Swwager.zip

    本项目“Springboot+SpringSecurity+SpringSession+Redis+Mybatis-Plus+Swwager”整合了Spring Boot、Spring Security、Spring Session、Redis、Mybatis-Plus以及Swagger等技术,旨在构建一个强大的、安全的、具有...

    SpringBoot集成Spring Security登录管理 添加 session 共享【完整源码+数据库】

    在本文中,我们将深入探讨如何在SpringBoot应用中集成Spring Security进行登录管理,并实现session共享。这是一项关键的安全措施,可以确保用户会话在多个微服务之间的一致性。我们将基于给定的标签——SpringBoot、...

    spring-session实现session共享

    Spring-Session作为一个优秀的解决方案,它结合了Redis等分布式存储,有效地解决了这个问题,实现了跨域和多应用之间的Session统一存储,进而支持单点登录(Single Sign-On, SSO)。 Spring-Session是Spring社区...

Global site tag (gtag.js) - Google Analytics