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

news2025/7/16 15:27:08

系列文章: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;
    }
    @Override
    public HttpSession getSession() {
        return this.getSession(true);
    }

    @Override
    public 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
  # 配置redis
  redis:
    host: 172.26.1.152
    port: 6379
    database: 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.coloradmin.cn/o/363031.html

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

相关文章

Python + Selenium,分分钟搭建 Web 自动化测试框架!

在程序员的世界中&#xff0c;一切重复性的工作&#xff0c;都应该通过程序自动执行。「自动化测试」就是一个最好的例子。 随着互联网应用开发周期越来越短&#xff0c;迭代速度越来越快&#xff0c;只会点点点&#xff0c;不懂开发的手工测试&#xff0c;已经无法满足如今的…

【Datawhale图机器学习】DeepWalk和Node2Vec

DeepWalk&#xff1a;用于图节点嵌入的在线机器学习算法 论文介绍 DeepWalk是基于随机游走的图节点嵌入算法。首次将深度学习和自然语言处理思想用于图机器学习&#xff0c;将随机游走序列与句子类比&#xff0c;节点与单词类比&#xff0c;构建Word2Vec的Skip-Gram无监督&am…

了解Axios及其运用方式

Axios简介 axios框架全称&#xff08;ajax – I/O – system&#xff09;&#xff1a; 基于promise用于浏览器和node.js的http客户端&#xff0c;因此可以使用Promise API 一、axios是干啥的 说到axios我们就不得不说下Ajax。在旧浏览器页面在向服务器请求数据时&#xff0c;…

计算机网络基础知识

目录 通信基础 前言 广播域与冲突域 计算机之间的连接方式 网线直连&#xff08;交叉线&#xff09; 同轴电缆 集线器 网桥 前言 举例&#xff08;计算机6向计算机7相互通信&#xff09; 交换机 交换机原理 路由器 路由器与其他设备区别&#xff1a; 注意&#…

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;就会执…

【历史上的今天】2 月 22 日:Red Hat Enterprise Linux 问世;BASIC 语言作者出生;计算机协会创始人诞生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 2 月 22 日&#xff0c;在 1857 年的今天&#xff0c;德国物理学家海因里希赫兹&#xff08;Heinrich Hertz&#xff09;出生。赫兹于 1887 年首先用实验证实了…

Mysql数据备份

一.数据备份的意义&#xff08;1&#xff09;保护数据的安全&#xff1b;&#xff08;2&#xff09;在出现意外的时候&#xff08;硬盘的损坏&#xff0c;断电&#xff0c;黑客的攻击&#xff09;&#xff0c;以便数据的恢复&#xff1b;&#xff08;3&#xff09;导出生产的数…

【音频处理和分析工具】上海道宁与NUGEN Audio助力您更轻松地提供高质量、合规的音频

NUGEN Audio的产品 可在任何情况下提供 先进的保真度和 不受限制的创造力 提供直接和直观的声音处理方式 NUGEN工具可以更轻松地 提供高质量、合规的音频 同时节省时间 降低成本并保留创作过程 开发商介绍 NUGEN Audio是后期制作、音乐和广播领域的知名品牌&#xff0c…

【Mysql】 锁

【Mysql】 锁 文章目录【Mysql】 锁1. 锁1.1 概述1.2 全局锁1.2.1 介绍1.2.2 语法1.2.2.1 加全局锁1.2.2.2 数据备份1.2.2.3 释放锁1.2.3 特点1.3 表级锁1.3.1 介绍1.3.2 表锁1.3.3 元数据锁1.3.4 意向锁1.4 行级锁1.4.1 介绍1.4.2 行锁1.4.3 间隙锁&临键锁1. 锁 1.1 概述…

一起学习用Verilog在FPGA上实现CNN----(八)integrationFC设计

1 integrationFC设计 LeNet-5网络结构全连接部分如图所示&#xff0c;该部分有2个全连接层&#xff0c;1个TanH激活层&#xff0c;1个SoftMax激活层&#xff1a; 图片来自附带的技术文档《Hardware Documentation》 integrationFC部分原理图&#xff0c;如图所示&#xff0c;…

python基于flask共享单车系统vue

可定制框架:ssm/Springboot/vue/python/PHP/小程序/安卓均可开发 目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2. 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.1.2技术可行性 6 3.1.3运行可行…

应用层协议

目录 应用层常见协议 DNS协议 前言 域名结构 DNS服务器分类 DNS的工作原理 DNS工作原理实例 DNS记录 DHCP协议 静态IP与动态IP DHCP协议好处 DHCP分配IP地址的4阶段 电子邮件 邮件的过程 电子邮件发送过程 pop协议特点 IMAP协议的特点 FTP协议 前言 FTP数据…

操作系统-初次理解

目录 1. 冯诺依曼体系 2. 操作系统 2.1 概念 2.2 解释 2.3 为什么管理 1. 冯诺依曼体系 我相信大家在学习计算机语言时一定听过这个体系结构的&#xff0c;那么这个结构到底是什么呢&#xff1f;上图&#xff1a; 该图是我对冯诺依曼体系结构简单构造&#xff0c;真实情况更…

Java实现多线程有几种方式(满分回答)

目录JDK8 创建的线程的两种方式orcle文档解释方式一&#xff1a;继承Thread类方式二&#xff1a;实现Runnable接口同时用两种的情况其他间接创建方式Callable接口线程池JDK8 创建的线程的两种方式 orcle文档解释 orcle文档&#xff1a;https://docs.oracle.com/javase/8/docs…

【项目精选】动漫论坛的设计与实现(论文+视频+源码)

点击下载源码 作为文化产业的一部分&#xff0c;动漫影响了我国一代又一代青少年&#xff0c;据钱江晚报调查显示&#xff0c;有超过七成的95后愿意从事与动漫相关的行业&#xff0c;可见其对青少年影响力之大。 动漫论坛作为最先开始热爱动漫人士进行交流的方式之一&#xff0…

让师生“不跑腿”,教育数据治理究竟有何魔力

当前&#xff0c;教育信息化新基础设施正在加紧建设&#xff0c;教育业务系统应用不断推进&#xff0c;各种软硬件平台源源不断地产生着教育数据。海量数据的汇聚和分析&#xff0c;能给教育系统带来什么&#xff1f;如何在教育数字化转型中&#xff0c;探索出基于数据驱动的新…

Linux 浅谈之性能分析工具 perf

Linux 浅谈之性能分析工具 perf HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是 Linux 浅谈系列&#xff0c;收录在操作系统专栏中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列将记录一些阿呆个人整理的 OS 相关知识…

【数据结构入门】-链表之单链表(1)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 文章标题回顾链表链表的概念及结构各种节点打印链表尾插创建节点尾删头插头删查找在pos…

微信小程序 Vue+uniapp+nodejs早教育教幼教知识学习系统

目 录 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系统…

PHP7.4 FFI 扩展安全问题

在前面 [极客大挑战 2020] 的Roamphp5-FighterFightsInvincibly 题&#xff0c;遇到了 FFI扩展 调用函数进行rce to bypass disable_function&#xff0c;之前没遇见过&#xff0c;刚好借此机会学一学 目录 <1> PHP 7.4 FFI简介 <2> FFI 配置信息 <3> FF…