Spring中自定义Session管理,Spring Session源码解析

news/2024/4/23 21:51:27/文章来源:https://blog.csdn.net/perfect2011/article/details/129142556

系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点

目录

系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点

1、session是什么?

1>session在哪里?

2>服务器怎么知道每次说话的是哪个session

3>session的使用

2、session的数据结构

3、tomcat中的session

3.1 session的创建时机和同步

3.2 session调用堆栈

3.3 源码分析

4、自定义一个Session的存储和构造,替换掉tomcat的session机制

4.1 思路

4.2 自定义session创建和管理

4.2.1>创建一个springboot项目

4.2.2> 创建一个自定义的session

4.2.3>创建一个session的容器,这里可以换成你想要的

4.2.4 替换servlet的实现

4.2.5 创建filter

4.2.6 测试controller

4.2.7 测试一下

5、Spring-session配置

5.1 创建springboot项目,

5.2 开启配置@EnableRedisHttpSession

5.3 配置文件

5.4 测试

5.5 确认数据存储到redis

6、Spring Session 源码分析

6.1 SessionRepositoryFilter

6.2 SessionRepository

7、总结


1、session是什么?

session用中文翻译就是会话,主要是为了维护web访问时的上下文信息保存,避免出现说完话就忘了对方是谁的情况

1>session在哪里?

session存储在服务器的内容中

2>服务器怎么知道每次说话的是哪个session

http访问的时候,header中有一个属性是sessionId,服务器根据session查找当前存在的属性

以chrome浏览器为例,访问一个基于tomcat服务器的网站的时候,

浏览器第一次访问服务器,服务器会在响应头添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客户端设置cooki

3>session的使用

//    获取session对象
HttpSession session = request.getSession();
//    获取
Object key = session.getAttribute("key");
session.setAttribute("key","value");

2、session的数据结构

session的数据结构没有固定的,怎么实现是看实现方式,这里介绍下常规认识的tomcat中的session数据结构

在tomcat中,session的数据结构是ConcurrentMap,key 是属性名,value是属性值

参考:org.apache.catalina.session.StandardSession

protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap();

后面我们要自定义一个session的存储方式。

3、tomcat中的session

3.1 session的创建时机和同步

客户端第一次请求request.getsession()时,也就是说客户端的请求中服务端第一次调用request.getsession()时,服务器会创建了session对象并保存在servlet容器的session集合中,同时生成一个sessionId,

向客户端发送要求设置cookie的响应(cookie中设置session id信息),客户端收到响应后,在客户端设置了一个jsessionid=xxxxxxx的cookie信息;

接下来客户端每次向服务器发送请求时,请求头都会带上该cookie信息(包含session id),那么之后的每次请求都能从servlet容器的session集合中找到客户端对应的session了,这样也就相当于保持了用户与服务器的交互状态。 

3.2 session调用堆栈

调试代码:

@GetMapping("/test/{id}")
public String test(@PathVariable(value = "id") String id, HttpServletRequest request) {HttpSession session = request.getSession();Long currentTime = timeService.getCurrentTime(id);System.out.println("参数 : "+ id +"   result  "+ currentTime);return "Hello";
}

看下断点的位置

3.3 源码分析

直接跟代码

HttpSession session = request.getSession();

下面流程画下来了,建议有条件的话跟一下代码,大概了解下

4、自定义一个Session的存储和构造,替换掉tomcat的session机制

4.1 思路

先看下tomcat整个数据的流程

思路1:直接替换掉tomcat的contextManger ,复杂了点,而且不适合Springboot,因为tomcat是内嵌的,如果换了web容器不一定适合

思路2:直接替换掉servlet中的session实现,在刚进入web容器的时候直接替换掉session实现,也就是在filter处

4.2 自定义session创建和管理

这里采用方案2,创建一个 filter,然后在入口处直接替换掉 request 中session的实现,直接上代码吧

4.2.1>创建一个springboot项目

不多说,一路next

4.2.2> 创建一个自定义的session

package com.xin.sessiontest.customize;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import java.util.*;public class CustomizeSession implements HttpSession {private final Map<String, Object> attributeMap = new LinkedHashMap<>();private boolean isNew;private String id;public CustomizeSession(String id) {if (id == null || "".equals(id.trim())) {id = UUID.randomUUID().toString().replace("-", "");}this.id = id;this.isNew = true;System.out.println("新session id : " + this.id);}public long getCreationTime() {return 0;}public String getId() {return this.id;}public long getLastAccessedTime() {return 0;}public ServletContext getServletContext() {return null;}public void setMaxInactiveInterval(int interval) {}public int getMaxInactiveInterval() {return 0;}public HttpSessionContext getSessionContext() {return null;}public Object getAttribute(String name) {return this.attributeMap.get(name);}public Object getValue(String name) {return null;}public Enumeration getAttributeNames() {System.out.println("CustomizeSession的getAttributeNames方法");return Collections.enumeration(this.attributeMap.keySet());}public String[] getValueNames() {return new String[0];}public void setAttribute(String name, Object value) {this.attributeMap.put(name, value);}public void putValue(String name, Object value) {}public void removeAttribute(String name) {}public void removeValue(String name) {}public void invalidate() {}public boolean isNew() {return this.isNew;}public void setIsNew(boolean isNew) {this.isNew = isNew;}
}

4.2.3>创建一个session的容器,这里可以换成你想要的

key 是sessionId,value是session

package com.xin.sessiontest.customize;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class CustomizeSessionContainer {public static final String DEFAULT_SESSION_ID_NAME = "JSESSIONID";private static final Map<String, CustomizeSession> sessionMap = new ConcurrentHashMap<>();public static CustomizeSession getSession(String sessionId, HttpServletResponse response) {return getSession(sessionId, true, response);}public static CustomizeSession getSession(String sessionId, boolean create, HttpServletResponse response) {if (sessionId == null) {sessionId = "";}CustomizeSession session = sessionMap.get(sessionId);if (session != null) {session.setIsNew(false);return session;}if (create) {session = new CustomizeSession(sessionId);sessionMap.put(session.getId(), session);response.addCookie(new Cookie(CustomizeSessionContainer.DEFAULT_SESSION_ID_NAME,session.getId()));}return session;}
}

4.2.4 替换servlet的实现

package com.xin.sessiontest.customize;
import javax.servlet.http.*;
public class CustomizeHttpServletRequest extends HttpServletRequestWrapper {private HttpServletResponse response;public CustomizeHttpServletRequest(HttpServletRequest request, HttpServletResponse response) {super(request);this.response = response;}@Overridepublic HttpSession getSession() {return this.getSession(true);}@Overridepublic HttpSession getSession(boolean create) {Cookie[] cookies = this.getCookies();String sessionId = "";if (cookies != null) {for (Cookie cookie : cookies) {if (CustomizeSessionContainer.DEFAULT_SESSION_ID_NAME.equals(cookie.getName())) {sessionId = cookie.getValue();break;}}}HttpSession customizeSession = CustomizeSessionContainer.getSession(sessionId, create, response);return customizeSession;}
}

4.2.5 创建filter

package com.xin.sessiontest.filter;import com.xin.sessiontest.customize.CustomizeHttpServletRequest;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/*", filterName = "FirstFilter")
public class CustomizeSessionFilter implements Filter {public void init(FilterConfig filterConfig) throws ServletException {System.out.println("CustomizeSessionFilter init");}public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {CustomizeHttpServletRequest request = new CustomizeHttpServletRequest((HttpServletRequest) req, (HttpServletResponse) resp);chain.doFilter(request, resp);}public void destroy() {System.out.println("CustomizeSessionFilter destroy");}
}

4.2.6 测试controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
public class TestController {@GetMapping("/")public String test(HttpServletRequest request){HttpSession session = request.getSession();session.setAttribute("key","value");return "hello";}
}

4.2.7 测试一下

http://localhost:8080/

看到后台是有打印session创建,证明已经接管了

5、Spring-session配置

Spring Session 它提供一组 API 和实现, 用于管理用户的 session 信息, 专注于解决 session 管理问题,轻易把session存储到第三方存储容器,框架提供了redis、jvm的map、mongo、gemfire、hazelcast、jdbc等多种存储session的容器的方式。主要解决分布式session共享问题

5.1 创建springboot项目,

勾选Spring Session 和Spring Data Redis

或者直接贴进去依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-core</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

5.2 开启配置@EnableRedisHttpSession

@SpringBootApplication
@EnableRedisHttpSession
public class SessionTestApplication {public static void main(String[] args) {SpringApplication.run(SessionTestApplication.class, args);}
}

5.3 配置文件

spring:# 选择redis为session存储session:store-type: redis# 配置redisredis:host: 172.26.1.152port: 6379database: 9# springsSession过期时间
server:servlet:session:timeout: 30m

5.4 测试

http://localhost:8080/

@RestController
public class TestController {@GetMapping("/")public String test(HttpServletRequest request){HttpSession session = request.getSession();session.setAttribute("caraway","香菜");return "hello";}
}

5.5 确认数据存储到redis

配置到9号DB,这里可以看到有一个key :caraway

6、Spring Session 源码分析

@EnableRedisHttpSession 导入了一个RedisHttpSessionConfiguration.class配置,

这个配置首先首先添加了一个组件RedisOperationsSessionRepository,redis操作session的dao其内部所有的方法都是增删改查的

看下百度到的图,貌似和我们实现的很像

6.1 SessionRepositoryFilter

用来切换HttpSession至Spring Session,包装HttpServletRequest和HttpServletResponse

@Order(-2147483598)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);SessionRepositoryFilter<S>.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);try {filterChain.doFilter(wrappedRequest, wrappedResponse);} finally {wrappedRequest.commitSession();}
}
}

看下重点:

1、order注解,是int的最小值,这个保证所有的请求第一个经过这个filter

2、doFilterInternal 包装request 和response

6.2 SessionRepository

看下实现类,看起来实现没啥,创建一个session,保存session数据,查找session,都是常规操作

public class RedisSessionRepository implements SessionRepository<RedisSession> {public RedisSession createSession() {MapSession cached = new MapSession();cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);RedisSession session = new RedisSession(cached, true);session.flushIfRequired();return session;}public void save(RedisSession session) {if (!session.isNew) {String key = this.getSessionKey(session.hasChangedSessionId() ? session.originalSessionId : session.getId());Boolean sessionExists = this.sessionRedisOperations.hasKey(key);if (sessionExists == null || !sessionExists) {throw new IllegalStateException("Session was invalidated");}}session.save();}public RedisSession findById(String sessionId) {String key = this.getSessionKey(sessionId);Map<String, Object> entries = this.sessionRedisOperations.opsForHash().entries(key);if (entries.isEmpty()) {return null;} else {MapSession session = (new RedisSessionMapper(sessionId)).apply(entries);if (session.isExpired()) {this.deleteById(sessionId);return null;} else {return new RedisSession(session, false);}}}
}

7、总结

全篇写了很多,简单来说就是session的管理,从web项目中接管session。

自定义session管理和Spring session 都是相同的原理

嵌入Filter,替换Request,自定义Session容器

源码下载地址:https://download.csdn.net/download/perfect2011/87472028

最后的最后求一个免费的赞同,爱心发电 

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

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

相关文章

H5盲盒抽奖系统源码

盲盒抽奖系统4.0&#xff0c;带推广二维码防洪炮灰功能和教程。 支持微信无限回调登录 标价就是源码价格&#xff0c;vuetp5框架编写&#xff0c;H5网页&#xff0c;前后端分离 此源码为正规开发&#xff0c;正版产品已申请软著。 开源无加密无授权&#xff0c;可以二开使用…

霍尔元件的应用

霍尔传感器有3个pin&#xff0c;分别是 正极 负极和输出pin。 输出pin接电阻和发光二极管。电阻起限流作用。 电源接5.5v直流电。当拿一个磁铁的N极靠近霍尔元件时&#xff0c;二极管越来越亮。当拿S极靠近霍尔元件时&#xff0c;二极管越来越暗。 N极磁场强度定义为正的磁场强…

算法刷题日志——移除元素,双指针

文章目录删除有序数组中的重复项[删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/)移除元素[283. 移动零](https://leetcode.cn/problems/move-zeroes/description/)[844. 比较含退格的字符串](https://leetcode.cn/problem…

Docker之路(3.docker底层原理、和虚拟机VM对比区别)

1.docker run 流程图 2. docker 底层原理 2.1 docker 是怎么工作的&#xff1f; Docker 是一个 Client-Server 结构的系统&#xff0c;Docker的守护进程运行在主机上&#xff0c; 通过Socket从客户端访问&#xff01; DockerServer接收到Docker-Client的指令&#xff0c;就会执…

【Java】Spring的创建和使用

Spring的创建和使用 Spring就是一个包含众多工具方法的IOC容器。既然是容器&#xff0c;那么就具备两个最主要的功能&#xff1a; 将对象存储到容器中从容器中将对象取出来 在Java语言当中对象也叫作Bean。 1. 创建Spring项目 创建一个普通maven项目添加Spring框架支持(spri…

Spring Boot自动装配的原理

Spring Boot自动装配的原理自动装配的实现EnableAutoConfigurationAutoConfigurationImportSelectorSpring Boot中的自动装配&#xff0c;它是Starter的基础&#xff0c;也是Spring Boot的核心。那么什么叫自动装配呢&#xff1f;或者说什么叫装配呢&#xff1f; 简单来说&…

金三银四丨黑蛋老师带你剖析-安全开发岗

作者丨黑蛋在之前呢&#xff0c;我们聊了二进制这块的病毒岗位&#xff0c;漏洞岗位&#xff0c;逆向岗位以及CTF这块的岗位。今天我们就来聊一聊安全开发类的工作岗位。首先网络安全方向中安全开发岗位都有哪些&#xff0c;安全开发主要指安全研发工程师或安全开发工程师&…

电子技术——负反馈特性

电子技术——负反馈特性 本节我们进一步深入介绍负反馈特性。 增益脱敏性 假设 β\betaβ 是一个常数。考虑下面的微分方程&#xff1a; dAfdA(1Aβ)2dA_f \frac{dA}{(1 A\beta)^2} dAf​(1Aβ)2dA​ 将上式除以 AfA1AβA_f \frac{A}{1A\beta}Af​1AβA​ 得到&#xff1…

php+vue加油站会员服务系统 java微信小程序

目 录 1绪论 1 1.1项目研究的背景 1 1.2开发意义 1 1.3项目研究现状及内容 5 1.4论文结构 5 2开发技术介绍 7 2.5微信小程序技术 8 3系统分析 9 3.1可行性分析 9 3.1.1技术可行性 9 3.1.2经济可行性 9 3.1.3操作可行性 10 3.2网站性能需求分析 10 3.3网站功能分析 10 3.4系统…

秒懂算法 | 子集树模型——0-1背包问题的回溯算法及动态规划改进

给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为W。一种物品要么全部装入背包,要么全部不装入背包,不允许部分装入。装入背包的物品的总重量不超过背包的容量。问应如何选择装入背包的物品,使得装入背包中的物品总价值最大? 01、问题分析——解空间及搜索…

JAVA并发集合之ConcurrentHashMap

ConcurrentHashMap是一个支持高并发更新与查询的哈希表(基于HashMap)。Hashmap在多线程并发的情况下&#xff0c;是线程不安全的&#xff0c;容易出现死循环、死锁等问题&#xff0c;JDK8后不会出现死锁问题&#xff0c;但依然存在多线程的系列问题&#xff0c;如&#xff1a;数…

关于虚拟数字人你想知道的都在这里

2022年底&#xff0c;微软旗下的人工智能实验室Open AI发布的对话式大型语言模型ChatGPT聊天机器人一夜蹿红&#xff0c;5天用户量超百万&#xff0c;在各大中外媒体平台掀起了一阵热潮。也带火了人工智能相关产业&#xff0c;AI虚拟数字人就是其中之一&#xff0c;一个随着元宇…

【电商】红冲与单价调整

产品及系统的规划与设计过程中始终会考虑实际生产环境中的异常场景&#xff0c;这样会增加系统复杂度&#xff0c;虽然有时可以通过简化流程来降低出现异常的概率&#xff0c;但很多时候都是无法避开的&#xff1b;本篇就简单梳理下红冲单与价格调整单方面的内容&#xff0c;希…

Java ”框架 = 注解 + 反射 + 设计模式“ 之 注解详解

Java ”框架 注解 反射 设计模式“ 之 注解详解 每博一文案 刹那间我真想令时光停住&#xff0c;好让我回顾自己&#xff0c;回顾失去的年华&#xff0c;缅怀哪个穿一身短小的连衣裙 和瘦窄的短衫的小女孩。让我追悔少年时代&#xff0c;我心灵的愚钝无知&#xff0c;它轻易…

解决 NestHost requires ASM7 (shrink、kotlin metadata)

① 场景 Caused by: java.lang.RuntimeException: NestHost requires ASM7Failed to resolve class org/vigame/demo/CrashHandler$1.class[transform input:not foundproject input:not foundaar input:not found]Caused by: java.lang.UnsupportedOperationException: NestH…

损失函数与反向传播

一、损失函数计算实际输出和目标之间的差距为我们更新输出提供一定的依据&#xff08;反向传播&#xff09;1.nn.L1Lossimport torch from torch.nn import L1Loss inputs torch.tensor([1,2,3],dtypetorch.float) targets torch.tensor([1,2,5],dtypetorch.float) # reshape…

ZED相机快速使用指南

1、安装SDK ZED SDK 3.8 - Download | Stereolabs 2、安装ros GitHub - stereolabs/zed-ros-wrapper: ROS wrapper for the ZED SDK 其他教程&#xff1a;ZED2相机SDK安装使用及ROS下使用_可即的博客-CSDN博客 3、官方文档 Get Started with ZED | Stereolabs 4、标定参…

「TCG 规范解读」第12章 TPM工作组 TCG身份验证研讨

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…

游戏蓝牙耳机哪个品牌好?游戏蓝牙耳机品牌排行榜

手机端的TWS耳机已成为主流&#xff0c;因而许多厂商也在制造蓝牙耳机时&#xff0c;不仅仅只限于音质&#xff0c;并且在延迟和功能上有所改进&#xff0c;下面小编整理了游戏蓝牙耳机品牌排行榜&#xff0c;看看有哪些入围的吧&#xff01; 一、南卡小音舱蓝牙耳机 蓝牙版本…

华为OD机试 - 计算网络信号(C++) | 附带编码思路 【2023】

刷算法题之前必看 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12199283.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 华为OD机试题…