博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[Spring] spring-session + JedisPool 实现 session 共享
阅读量:6888 次
发布时间:2019-06-27

本文共 13518 字,大约阅读时间需要 45 分钟。

 

1、至少导入四个jar包:

jedisspring-session spring-data-redis commons-pool2

 

2、bean配置

 

3、web.xml中配置过滤器

   
  
springSessionRepositoryFilter
org.springframework.web.filter.DelegatingFilterProxy
springSessionRepositoryFilter
/*

 

 

拓展:

【】http://docs.spring.io/spring-session/docs/current/reference/html5/

【】http://www.sjsjw.com/104/002313MYM000506/

【】 http://dorole.com/1422/

【】http://doc.okbase.net/Hello_Nick_Xu/archive/92878.html

1、为什么需要spring-session?它主要解决了哪方面的问题?

 在单台Tomcat应用中,通常使用session保存用户的会话数据。

 面对高并发的场景,一台Tomcat难当大任,通常我们会使用Nginx在前端拦截用户请求,转发给后端的Tomcat服务器群组。在集群环境下,怎么才能做到session数据在多台Tomcat之间的共享呢?

 当然我们可以在多台Tomcat之间进行session数据的相互复制。这样做的代价是巨大的,尤其是后端Tomcat服务器比较多的情况下(几十台、甚至上百台Tomcat),session数据在Tomcat之间的相互复制,将消耗大量的系统开销、甚至引发网络广播风暴,影响服务器的正常运行。

 这时候可以考虑将session数据进行集中存储,比较常见的是使用Memcached来存放会话数据。

 但是使用Memcached有着诸多限制,比如:对存放对象大小的限制、无法进行数据的持久化等。一般使用Redis来充当Memcached的角色,同时使用Spring Data Redis来完成对Redis的操作。

 ====================================================================================================

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

 那么问题就来了,这样可能导致在某次访问时获取不到session,如何保证不同的web站点能够共享同一份session数据呢?

 最简单的想法就是把session数据保存到内存以外的一个统一的地方,例如Memcached/Redis等数据库中。那么问题又来了,如何替换掉Servlet容器创建和管理HttpSession的实现呢? 

(1)设计一个Filter,利用HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作。spring-session就是通过这样的思路实现的。   这里也有一个【】例子,就是仿造这种思路实现的。   http://blog.csdn.net/ppt0501/article/details/46700221(2)利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。不过这种方式有个缺点,就是需要耦合Tomcat/Jetty等Servlet容器的代码。这方面其实早就有开源项目了,例如memcached-session-manager,以及tomcat-redis-session-manager。暂时都只支持Tomcat6/Tomcat7。

 

2、什么是spring-session?

Spring Session是Spring的项目之一,GitHub地址:https://github.com/spring-projects/spring-session。 Spring Session提供了一套创建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。 下面是来自官网的特性介绍: Spring Session provides the following features: API and implementations for managing a user's sessionHttpSession - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral wayClustered 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 APIsWebSocket - provides the ability to keep the HttpSession alive when receiving WebSocket messages

 

3、集成SpringSession的4个正确姿势

(1)导入相关jar/配置相关依赖

(2)编写一个配置类,用来启用RedisHttpSession功能,并向Spring容器中注册一个RedisConnectionFactory。/或者通过xml配置

import org.springframework.context.annotation.Bean;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200)public class RedisHttpSessionConfig {    @Bean    public RedisConnectionFactory connectionFactory() {        JedisConnectionFactory connectionFactory = new JedisConnectionFactory();        connectionFactory.setPort(6379);        connectionFactory.setHostName("10.18.15.190");        return connectionFactory;    }}

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

//该类采用Java Configuration,来代替web.xml   public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {        @Override    protected Class
[] getRootConfigClasses() { return new Class[]{Config1.class, Config2.class, RedisHttpSessionConfig.class}; } //......}

 

(4)第四步,编写一个一个AbstractHttpSessionApplicationInitializer实现类,用于向Servlet容器中添加springSessionRepositoryFilter。  / 或者通过xml配置

import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;public class SpringSessionInitializer extends AbstractHttpSessionApplicationInitializer {}

 

4、SpringSession原理

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

而@EnableRedisHttpSession注解通过Import,引入了RedisHttpSessionConfiguration配置类。该配置类通过@Bean注解,向Spring容器中注册了一个SessionRepositoryFilter。

(SessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)

package org.springframework.session.data.redis.config.annotation.web.http;@Configuration@EnableSchedulingpublic class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoaderAware {    //......        @Bean    public RedisTemplate
sessionRedisTemplate(RedisConnectionFactory connectionFactory) { //...... return template; } @Bean public RedisOperationsSessionRepository sessionRepository(RedisTemplate
sessionRedisTemplate) { //...... return sessionRepository; } @Bean public
SessionRepositoryFilter
springSessionRepositoryFilter(SessionRepository sessionRepository, ServletContext servletContext) { //...... return sessionRepositoryFilter; } //......}

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

package org.springframework.session.web.context;/** * Registers the {
@link DelegatingFilterProxy} to use the * springSessionRepositoryFilter before any other registered {
@link Filter}. * * ...... */@Order(100)public abstract class AbstractHttpSessionApplicationInitializer implements WebApplicationInitializer { private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT."; public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter"; //...... public void onStartup(ServletContext servletContext) throws ServletException { beforeSessionRepositoryFilter(servletContext); if(configurationClasses != null) { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(configurationClasses); servletContext.addListener(new ContextLoaderListener(rootAppContext)); } insertSessionRepositoryFilter(servletContext);//注册一个SessionRepositoryFilter afterSessionRepositoryFilter(servletContext); } /** * Registers the springSessionRepositoryFilter * @param servletContext the {
@link ServletContext} */ private void insertSessionRepositoryFilter(ServletContext servletContext) { String filterName = DEFAULT_FILTER_NAME;//默认名字是springSessionRepositoryFilter DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);//该Filter代理会在初始化时从Spring容器中查找springSessionRepositoryFilter,之后实际会使用SessionRepositoryFilter进行doFilter操作 String contextAttribute = getWebApplicationContextAttribute(); if(contextAttribute != null) { springSessionRepositoryFilter.setContextAttribute(contextAttribute); } registerFilter(servletContext, true, filterName, springSessionRepositoryFilter); } //......}

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

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

@Order(SessionRepositoryFilter.DEFAULT_ORDER)public class SessionRepositoryFilter implements Filter {        public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {                HttpServletRequest httpRequest = (HttpServletRequest) request;                SessionRepositoryRequestWrapper customRequest =                        new SessionRepositoryRequestWrapper(httpRequest);                chain.doFilter(customRequest, response, chain);        }        // ...}public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {        public SessionRepositoryRequestWrapper(HttpServletRequest original) {                super(original);        }        public HttpSession getSession() {                return getSession(true);        }        public HttpSession getSession(boolean createNew) {                // create an HttpSession implementation from Spring Session        }        // ... other methods delegate to the original HttpServletRequest ...}

(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实现类。
package org.springframework.web;@HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer {    /**     * Delegate the {
@code ServletContext} to any {
@link WebApplicationInitializer} * implementations present on the application classpath. * *

Because this class declares @{

@code HandlesTypes(WebApplicationInitializer.class)}, * Servlet 3.0+ containers will automatically scan the classpath for implementations * of Spring's {
@code WebApplicationInitializer} interface and provide the set of all * such types to the {
@code webAppInitializerClasses} parameter of this method. * *

If no {

@code WebApplicationInitializer} implementations are found on the * classpath, this method is effectively a no-op. An INFO-level log message will be * issued notifying the user that the {
@code ServletContainerInitializer} has indeed * been invoked but that no {
@code WebApplicationInitializer} implementations were * found. * *

Assuming that one or more {

@code WebApplicationInitializer} types are detected, * they will be instantiated (and sorted if the @{
@link * org.springframework.core.annotation.Order @Order} annotation is present or * the {
@link org.springframework.core.Ordered Ordered} interface has been * implemented). Then the {
@link WebApplicationInitializer#onStartup(ServletContext)} * method will be invoked on each instance, delegating the {
@code ServletContext} such * that each instance may register and configure servlets such as Spring's * {
@code DispatcherServlet}, listeners such as Spring's {
@code ContextLoaderListener}, * or any other Servlet API componentry such as filters. * * @param webAppInitializerClasses all implementations of * {
@link WebApplicationInitializer} found on the application classpath * @param servletContext the servlet context to be initialized * @see WebApplicationInitializer#onStartup(ServletContext) * @see AnnotationAwareOrderComparator */ @Override public void onStartup(Set
> webAppInitializerClasses, ServletContext servletContext) throws ServletException { //...... }}

 

5、如何在 redis 中查看 Session 数据?

(1)Http Session数据在Redis中是以Hash结构存储的。 (2)可以看到,还有一个key="spring:session:expirations:1431577740000"的数据,是以Set结构保存的。这个值记录了所有session数据应该被删除的时间(即最新的一个session数据过期的时间)。
127.0.0.1:6379> keys *1) "spring:session:expirations:1431577740000"2) "spring:session:sessions:e2cef3ae-c8ea-4346-ba6b-9b3b26eee578" 127.0.0.1:6379> type spring:session:sessions:e2cef3ae-c8ea-4346-ba6b-9b3b26eee578hash 127.0.0.1:6379> type spring:session:expirations:1431577740000set127.0.0.1:6379> hkeys spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b1) "maxInactiveInterval"2) "creationTime"3) "lastAccessedTime"4) "sessionAttr:attr1"127.0.0.1:6379> hget spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b sessionAttr:attr1"\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"127.0.0.1:6379> hget spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b creationTime"\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"

 

你可能感兴趣的文章
debian7解决chrome中文乱码
查看>>
coalesce()
查看>>
kali linux 网络配置
查看>>
word双栏排版,最后一页由于分节符造成最后一页是空白页,删除分节符双栏就变成了单栏...
查看>>
服务设计要解决的问题
查看>>
H.264学习方法及部分基础知识
查看>>
春季学期第三周作业
查看>>
Windows API 编程----将错误代码转换成错误描述信息
查看>>
CentOS 7运维管理笔记(12)----GUI配置工具Webmin的安装
查看>>
Leetcode算法比赛----First Unique Character in a String
查看>>
HDU 5025 Saving Tang Monk
查看>>
HDU 1816 Get Luffy Out *
查看>>
Robot Framework + Selenium library + IEDriver环境搭建
查看>>
第220天:Angular---路由
查看>>
Android中XML解析-PULL解析
查看>>
mysql小记--基础知识
查看>>
Java验证码
查看>>
vue 编辑
查看>>
【状压DP】【NOIP提高组】愤怒的小鸟
查看>>
MVC, MVP, MVVM总结——MVC篇
查看>>