SpringSession原理简析

本文借鉴于:Spring-Session 原理简析 - 知乎 (zhihu.com)

目录

概述

使用方式

原理

总结


概述

Session的原理

Session是存在服务器的一种用来存放用户数据的类哈希表结构,当浏览器第一次发送请求的时候服务器会生成一个hashtable和一个sessionid,sessionid来唯一标识这个hashtable,响应的时候会通过一个响应头set-cookie返回给浏览器,浏览器再将这个sessionid存储在一个名为JESSIONID的cookie中。 接着浏览器在发送第二次请求时,就会带上这个cookie,这个cookie会存储sessionid,并发送给服务器,服务器根据这个sessionid找到对应的用户信息。

分布式下Session共享的问题

如果在不同域名环境下需要进行session共享,比如在auth.gulimall.com登录成功之后要将登陆成功的用户信息共享给gulimall.com服务,由于域名不同,普通的session就会不起作用。并且如果是同一个服务,复制多份,session也不会共享。

SpringSession

SpringSession 是一个 Spring 项目中用于管理和跟踪会话的框架。它提供了一种抽象层,使得会话数据可以存储在不同的后端数据结构中(例如内存、数据库、Redis 等),并且支持跨多个请求的会话管理。

Spring Session 的核心功能包括:

(1)跨多个请求共享会话数据:SpringSession 使用一个唯一的会话标识符来跟踪用户的会话,并且可以在不同的请求中共享会话数据,无论是在同一个应用程序的不同服务器节点之间,还是在不同的应用程序之间。

(2)多种后端存储支持:SpringSession 支持多种后端存储,包括内存、数据库和缓存系统(如 Redis)。你可以根据应用程序的需求选择合适的存储方式。

(3)会话过期管理:SpringSession 提供了会话过期管理的功能,可以根据一定的策略自动清理过期的会话数据。 会话事件监听:Spring Session 允许开发人员注册会话事件监听器,以便在会话创建、销毁或属性更改时执行一些自定义逻辑。

(4)使用 SpringSession 可以使得在分布式环境中管理会话变得更加简单和灵活,同时也提供了更多的扩展性和可定制性。

SpringSession 可以在多个微服务之间共享 session 数据。

使用方式

添加依赖

<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>

添加注解@EnableRedisHttpSession

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class RedisSessionConfig {}

maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。也可以在yml文件上配置

spring:  session:store-type: redistimeout: 30m #这个过期时间是在浏览器关闭之后才开始计时

经过上面的配置后,Session 调用就会自动去Redis存取。另外,想要达到 Session 共享的目的,只需要在其他的系统上做同样的配置即可。

原理

看了上面的配置,我们知道开启 Redis Session 的“秘密”在 @EnableRedisHttpSession 这个注解上。打开 @EnableRedisHttpSession 的源码(shift+shift):

/*** 用于启用基于Redis的HTTP会话管理的配置注解。* 该注解会将RedisHttpSessionConfiguration配置类导入到Spring配置中,* 从而支持将会话信息存储在Redis中。** @author <NAME>* @see RedisHttpSessionConfiguration*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration(proxyBeanMethods = false
)
public @interface EnableRedisHttpSession {/*** 会话的最大不活动间隔时间(秒)。默认值为1800秒(30分钟)。* 该属性定义了会话在Redis中存储的有效期。* * @return 最大不活动间隔时间(秒)*/int maxInactiveIntervalInSeconds() default 1800;/*** Redis中用于存储会话数据的命名空间。默认值为"spring:session"。* 通过设置不同的命名空间,可以实现多个应用会话的隔离。* * @return Redis存储会话数据的命名空间*/String redisNamespace() default "spring:session";/*** 已弃用的Redis刷新模式设置。建议使用{@link #flushMode()}替代。* * @return Redis刷新模式,默认为ON_SAVE* @deprecated 使用{@link #flushMode()}替代*/@DeprecatedRedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;/*** 定义Redis中数据的刷新模式。可配置为在每次保存属性时刷新(ON_SAVE),* 或者在请求结束时刷新(ON_COMMIT)。* * @return 刷新模式,默认为ON_SAVE*/FlushMode flushMode() default FlushMode.ON_SAVE;/*** 定义清理过期会话的CRON表达式。默认值为"0 * * * * *",即每分钟执行一次。* 通过调整该表达式,可以控制清理过期会话的频率。* * @return 清理过期会话的CRON表达式*/String cleanupCron() default "0 * * * * *";/*** 定义会话属性保存的模式。可配置为仅在设置属性时保存(ON_SET_ATTRIBUTE),* 或者在每次请求结束时保存(ALWAYS)。* * @return 会话属性保存模式,默认为ON_SET_ATTRIBUTE*/SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
}

这个注解的主要作用是注册一个 SessionRepositoryFilter,这个 Filter 会拦截所有的请求,对 Session 进行操作。

SessionRepositoryFilter 拦截到请求后,会先将 request 和 response 对象转换成 Spring 内部的包装类 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 对象。SessionRepositoryRequestWrapper 类重写了原生的getSession方法。代码如下:

@Override
public HttpSessionWrapper getSession(boolean create) {//通过request的getAttribue方法查找CURRENT_SESSION属性,有直接返回HttpSessionWrapper currentSession = getCurrentSession();if (currentSession != null) {return currentSession;}//查找客户端中一个叫SESSION的cookie,通过sessionRepository对象根据SESSIONID去Redis中查找SessionS requestedSession = getRequestedSession();if (requestedSession != null) {if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {requestedSession.setLastAccessedTime(Instant.now());this.requestedSessionIdValid = true;currentSession = new HttpSessionWrapper(requestedSession, getServletContext());currentSession.setNew(false);//将Session设置到request属性中setCurrentSession(currentSession);//返回Sessionreturn currentSession;}}else {// This is an invalid session id. No need to ask again if// request.getSession is invoked for the duration of this requestif (SESSION_LOGGER.isDebugEnabled()) {SESSION_LOGGER.debug("No session found by id: Caching result for getSession(false) for this HttpServletRequest.");}setAttribute(INVALID_SESSION_ID_ATTR, "true");}//不创建Session就直接返回nullif (!create) {return null;}if (SESSION_LOGGER.isDebugEnabled()) {SESSION_LOGGER.debug("A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "+ SESSION_LOGGER_NAME,new RuntimeException("For debugging purposes only (not an error)"));}//通过sessionRepository创建RedisSession这个对象,可以看下这个类的源代码,如果//@EnableRedisHttpSession这个注解中的redisFlushMode模式配置为IMMEDIATE模式,会立即//将创建的RedisSession同步到Redis中去。默认是不会立即同步的。S session = SessionRepositoryFilter.this.sessionRepository.createSession();session.setLastAccessedTime(Instant.now());currentSession = new HttpSessionWrapper(session, getServletContext());setCurrentSession(currentSession);return currentSession;
}

当调用 SessionRepositoryRequestWrapper 对象的getSession方法拿 Session 的时候,会先从当前请求的属性中查找CURRENT_SESSION属性,如果能拿到直接返回,这样操作能减少Redis操作,提升性能。

到现在为止我们发现如果redisFlushMode配置为 ON_SAVE 模式的话,Session 信息还没被保存到 Redis 中,那么这个同步操作到底是在哪里执行的呢?

仔细看代码,我们发现 SessionRepositoryFilter 的doFilterInternal方法最后有一个 finally 代码块,这个代码块的功能就是将 Session同步到 Redis。

@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response, this.servletContext);SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);try {filterChain.doFilter(wrappedRequest, wrappedResponse);}finally {//将Session同步到Redis,同时这个方法还会将当前的SESSIONID写到cookie中去,同时还会发布一//SESSION创建事件到队列里面去wrappedRequest.commitSession();}
}

总结

主要的核心类有:

  • EnableRedisHttpSession:开启 Session 共享功能;
  • RedisHttpSessionConfiguration:配置类,一般不需要我们自己配置,主要功能是配置 SessionRepositoryFilter 和 RedisOperationsSessionRepository 这两个Bean;
  • SessionRepositoryFilter:拦截器,Spring-Session 框架的核心;
  • RedisOperationsSessionRepository:可以认为是一个 Redis 操作的客户端,有在 Redis 中进行增删改查 Session 的功能;
  • SessionRepositoryRequestWrapper:Request 的包装类,主要是重写了getSession方法
  • SessionRepositoryResponseWrapper:Response的包装类。

原理简要总结:

当请求进来的时候,SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 。后续当第一次调用 request 的getSession方法时,会调用到 SessionRepositoryRequestWrapper 的getSession方法。这个方法是被重写过的,逻辑是先从 request 的属性中查找,如果找不到,再查找一个key值是"SESSION"的 Cookie,通过这个 Cookie 拿到 SessionId 去 Redis 中查找,如果查不到,就直接创建一个RedisSession 对象,同步到 Redis 中。

说的简单点就是:拦截请求,将之前在服务器内存中进行 Session 创建、销毁的动作,改成在 Redis 中进行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/14448.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读--Language-driven Semantic Segmentation

效果很好&#xff0c;文本增加一个词&#xff0c;就能找到对应的分割地方&#xff0c;给出的无用标签也不会去错误分割&#xff0c;而且能理解文本意思&#xff0c;例如dog和pet都能把狗给分割出来 image encoder使用DPT分割模型&#xff0c;大致架构为ViTdecoder&#xff0c;d…

【个人经历分享】末流本科地信,毕业转码经验

本人24届末流本科&#xff0c;地理信息科学专业。 我们这个专业可以说是 “高不成&#xff0c;低不就”的专业&#xff0c;什么都学但都不精。考研我实在是卷不动同学历的人&#xff0c;我在大三的时候就开始考虑转码。 至于我为什么选择转码&#xff0c;选择了GIS开发&#xf…

element ui 下拉框Select 选择器 上下箭头旋转方向样式错乱——>优化方案

目录 前言1、问题复现2、预期效果3、input框样式修改解析4、修改方案 &#x1f680;写在最后 前言 测试A&#xff1a;那啥&#xff01;抠图仔&#xff0c;样式怎么点着点着就出问题了。 前端&#xff1a;啥&#xff1f;css样式错乱了&#xff1f;你是不是有缓存啊&#xff01…

js常用数组方法

1.arr.push() -末尾添加 该方法可以向数组末尾添加一个或多个元素&#xff0c;并返回数组新的长度可以将要添加的元素作为方法的参数传递&#xff0c;这样这些元素将会自动添加到元素的末尾原数组会发生变化 var arr [ 1, 2, 3, 4 ] arr.push(5) console.log(arr) // [ 1, …

linux命令arp的使用

arp arp 命令用于显示和修改 IP 到 MAC 转换表 补充说明 arp 命令 是 Address Resolution Protocol&#xff0c;地址解析协议&#xff0c;是通过解析网络层地址来找寻数据链路层地址的一个网络协议包中极其重要的网络传输协议。而该命令可以显示和修改 arp 协议解析表中的缓…

Mia for Gmail for Mac:Mac用户的邮件管理首选

对于追求高效工作的Mac用户来说&#xff0c;Mia for Gmail for Mac无疑是邮件管理的首选工具。它以其卓越的性能和丰富的功能&#xff0c;为用户带来了前所未有的高效邮件管理体验。 Mia for Gmail for Mac不仅支持多帐号登录和标签选择功能&#xff0c;还提供了邮件分类、垃圾…

linux 中 fd 申请和释放管理(两级 bitmap)

linux 中 fd 的几点理解_linux fd-CSDN博客 通过上边的文章&#xff0c;我们可以知道&#xff0c;在 linux 中&#xff0c;fd 有以下几点需要了解&#xff1a; &#xff08;1&#xff09;fd 表示进程打开的文件&#xff0c;是进程级别的资源&#xff0c;不是系统级别的资源 …

【前端每日一题】day11

一个盒子(DIV)里有若干个小盒子&#xff0c;每个小盒子里还可能有多个小盒子 多层盒子结构。每个盒子都有一个唯一的id和 name 属性。现在给出一个盒子的 id 请找到这个盒子并打开&#xff0c;输出这个盒子内部所有小盒子的id和 name&#xff0c;并继续打开这些小盒子输出id和 …

【Unity】Unity项目转抖音小游戏(四)一些常用方法

1.初始化 SDK会在Unity启动前就初始化好&#xff0c;但是又有Init的接口&#xff0c;所以这里通过 StarkSDK.s_ContainerEnv 判断有没有初始化&#xff0c;没有的话就手动初始化 public override void Init(string code, Action callback){Debug.Log("初始化抖音SDK"…

AIGC全面介绍

AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;&#xff0c;即生成式人工智能&#xff0c;是人工智能1.0时代进入2.0时代的重要标志。这一技术的出现&#xff0c;标志着人工智能从计算智能、感知智能迈向了认知智能的新阶段。以下是关于AIGC的全面介绍…

基于manifest文件批量将coding的仓库导入gitlab中

文章目录 写在前面的话背景编写manifest文件最终效果 写在前面的话 前面有讲过通过manifest清单导入项目到gitlab中&#xff0c;但是实际的操作是不同gitlab实例之间的操作&#xff0c;然而对于在不同gitlab实例的repo迁移而言&#xff0c;显然direct transfer会更合适。 背景…

民国漫画杂志《时代漫画》第21期.PDF

时代漫画21.PDF: https://url03.ctfile.com/f/1779803-1248634754-017e2b?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

代码随想录算法训练营Day49 | 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV | Python | 个人记录向

本文目录 123.买卖股票的最佳时机III做题看文章 188.买卖股票的最佳时机IV做题 以往忽略的知识点小结个人体会 123.买卖股票的最佳时机III 代码随想录&#xff1a;123.买卖股票的最佳时机III Leetcode&#xff1a;123.买卖股票的最佳时机III 做题 无思路。 看文章 确定dp数…

结构型模式之桥接模式

文章目录 概述原理结构图代码示例 小结 概述 桥接模式(bridge pattern) 的定义是&#xff1a;将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。 桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,将类之间的静态继承关系转…

使用位掩码的权限设计

使用位掩码的权限设计 权限系统的设计几乎是每个系统都必需的模块。 下面就聊一聊基本设计的思路。 位掩码&#xff08;BitMask&#xff09;&#xff0c;是位&#xff08;Bit&#xff09;和掩码&#xff08;Mask&#xff09;的组合词。 “位”指代着二进制数据当中的二进制位…

基于深度学习OCR文本识别系统源码(带界面)

第一步&#xff1a;概要 基于深度学习OCR文本识别分为两个模块&#xff1a;DBNet和CRNN。 DBNet是基于分割的文本检测算法&#xff0c;算法将可微分二值化模块(Differentiable Binarization)引入了分割模型&#xff0c;使得模型能够通过自适应的阈值图进行二值化&#xff0c;并…

Postgresql 基础学习

一、介绍 PostgreSQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它支持SQL语言的所有功能&#xff0c;具有可扩展性、高并发性和可靠性等特点。 以下是一些 PostgreSQL 的特点&#xff1a; 开源&#xff1a;PostgreSQL是一个非常受欢迎的开源…

Python-温故知新

1快速打开.ipynb文件 安装好anaconda后&#xff0c;在需要打开notebook的文件夹中&#xff0c; shift键右键——打开powershell窗口——输入jupyter notebook 即可在该文件夹中打开notebook的页面&#xff1a; 2 快速查看函数用法 光标放在函数上——shift键tab 3...

Docker镜像源自动测试镜像速度,并选择速度最快的镜像

国内执行如下代码 bash <(curl -sSL https://gitee.com/xjxjin/scripts/raw/main/check_docker_registry.sh)国外执行如下代码 bash <(curl -sSL https://github.com/xjxjin/scripts/raw/main/check_docker_registry.sh)如果有老铁有比较不错的镜像源&#xff0c;可以提…

探索Python编程乐趣:制作气泡反弹小游戏

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;Python编程的轻松入门 二、游戏实现原理&#xff1a;气泡反弹的逻辑 …