SpringSession的源码解析(从Cookie中读取Sessionid,根据sessionid查询信息全流程分析)

简介: 上一篇我们介绍了SpringSession中Session的保存过程,今天我们接着来看看Session的读取过程。相对保存过程,读取过程相对比较简单。本文想从源码的角度,详细介绍一下Session的读取过程。

前言

上一篇我们介绍了SpringSession中Session的保存过程,今天我们接着来看看Session的读取过程。相对保存过程,读取过程相对比较简单。

本文想从源码的角度,详细介绍一下Session的读取过程。

读取过程的时序图

如上,是读取Session的时序图,首先代码入口还是SessionRepositoryFilter过滤器的doFilterInternal方法。这个方法里还是会调用到SessionRepositoryRequestWrapper类的getSession()方法,这个getSession方法是读取Session的开始,这个方法内部会调用getSession(true)方法。那我们就从SessionRepositoryRequestWrapper类的getSession(true)方法开始说起。

getSession(true)方法。

@Override
  public HttpSessionWrapper getSession(boolean create) {
    //获取HttpSessionWrapper类,这个类会包装HttpSession
    HttpSessionWrapper currentSession = getCurrentSession();
    if (currentSession != null) {
    return currentSession;
    }
    //获取RedisSession
    S 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);
      setCurrentSession(currentSession);
      return currentSession;
    }
    }
    //省略部分代码
  }

这个方法首先获取HttpSessionWrapper对象,这个对象的作用是用于封装session,返回给其上一层,如果可以获取到则说明Session信息已经拿到了,就直接返回。

如果获取不到则调用getRequestedSession()方法。这个方法就是获取session的主方法。接着让我们来看看这个方法吧。

getRequestedSession()方法

private S getRequestedSession() {
    if (!this.requestedSessionCached) {
    //从cookie中获取sessionid集合
    List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
      .resolveSessionIds(this);
    //遍历sessionid集合,分别获取HttpSession
    for (String sessionId : sessionIds) {
      if (this.requestedSessionId == null) {
      this.requestedSessionId = sessionId;
      }
      //根据sessionid去redis中获取session
      S session = SessionRepositoryFilter.this.sessionRepository
        .findById(sessionId);
      if (session != null) {
      this.requestedSession = session;
      this.requestedSessionId = sessionId;
      break;
      }
    }
    this.requestedSessionCached = true;
    }
    return this.requestedSession;
  }

如上,这个方法主要有两步:

1.从cookie中获取sessionid的集合,可能cookie中存在多个sessionid。

2.循环sessionid的集合,分别根据sessionid到redis中获取session。

获取sessionid是通过HttpSessionIdResolver接口的resolveSessionIds方法来实现的,SessionRepositoryFilter中定义了HttpSessionIdResolver接口的实例,其实现类是CookieHttpSessionIdResolver类。

private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();

所以,SessionRepositoryFilter.this.httpSessionIdResolver的实例是一个CookieHttpSessionIdResolver对象。

而SessionRepositoryFilter.this.sessionRepository的实例是一个RedisOperationsSessionRepository对象。

那么接下来我们就分别来看看这个两个类的相关方法。

resolveSessionIds方法

接下来,我们就来到了CookieHttpSessionIdResolver类的resolveSessionIds方法,这个方法主要的作用就是从cookie中获取sessionid。

@Override
  public List<String> resolveSessionIds(HttpServletRequest request) {
  return this.cookieSerializer.readCookieValues(request);
  }

看到这个方法之后,我们发现这个方法只是一个中转方法,内部直接把请求交给了readCookieValues方法。同样的在CookieHttpSessionIdResolver类内部也定义了cookieSerializer这个属性,

它的实例对象是DefaultCookieSerializer。所以,真正的操作逻辑还是在DefaultCookieSerializer类中完成的。

private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

接下来,我们就来看看DefaultCookieSerializer这个类的的readCookieValues方法。

readCookieValues方法

@Override
  public List<String> readCookieValues(HttpServletRequest request) {
  //从请求头中获取cookies
  Cookie[] cookies = request.getCookies();
  List<String> matchingCookieValues = new ArrayList<>();
  if (cookies != null) {
    for (Cookie cookie : cookies) {
    //获取存放sessionid的那个cookie,cookieName默认是SESSION
    if (this.cookieName.equals(cookie.getName())) {
      //默认的话sessionid是加密的
      String sessionId = (this.useBase64Encoding
        ? base64Decode(cookie.getValue())
        : cookie.getValue());
      if (sessionId == null) {
      continue;
      }
      if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
      sessionId = sessionId.substring(0,
        sessionId.length() - this.jvmRoute.length());
      }
      matchingCookieValues.add(sessionId);
    }
    }
  }
  return matchingCookieValues;
  }

如上,这个从cookie中获取sessionid的方法也很简单,无非就是从当前的HttpServletRequest对象中获取所有的cookie,然后,提取name等于cookieName的cookie值。

这个cookie值就是sessionid。

findById方法

从cookie中那个sessionid之后会调用RedisOperationsSessionRepository类的findById方法,这个方法的作用就是从redis中获取保存的session信息。

public RedisSession findById(String id) {
  //直接调用getSession方法
  return getSession(id, false);
  }
  private RedisSession getSession(String id, boolean allowExpired) {
  //获取当前session在redis保存的所有数据
  Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
  if (entries.isEmpty()) {
    return null;
  }
  //传入数据并组装成MapSession
  MapSession loaded = loadSession(id, entries);
  if (!allowExpired && loaded.isExpired()) {
    return null;
  }
  //将MapSession在转成RedisSession,并最终返回
  RedisSession result = new RedisSession(loaded);
  result.originalLastAccessTime = loaded.getLastAccessedTime();
  return result;
  }

如上,我们可以看到findById方法内部直接调用了getSession方法,所以,所有的逻辑都在这个方法,而这个方法的逻辑分为三步:

1.根据sessionid获取当前session在redis保存的所有数据

2.传入数据并组装成MapSession

3.将MapSession在转成RedisSession,并最终返回

我们一步步的看

首先,第一步根据sessionid获取当前session在redis保存的所有数据

private BoundHashOperations<Object, Object, Object> getSessionBoundHashOperations(
    String sessionId) {
  //拿到key
  String key = getSessionKey(sessionId);
  //根据key获取值
  return this.sessionRedisOperations.boundHashOps(key);
  }
  //key是spring:session sessions:+sessionid
  String getSessionKey(String sessionId) {
  return this.namespace + "sessions:" + sessionId;
  }

需要注意的是,session保存到redis中的值不是字符类型的。而是通过对象保存的,是hash类型。

总结

至此,从Cookie中读取SessionId,然后,根据SessionId查询保存到Redis中的数据的全过程,希望对大家有所帮助。

相关文章
|
12月前
|
存储 域名解析 弹性计算
阿里云上云流程参考:云服务器+域名+备案+域名解析绑定,全流程图文详解
对于初次通过阿里云完成上云的企业和个人用户来说,很多用户不仅是需要选购云服务器,同时还需要注册域名以及完成备案和域名的解析相关流程,从而实现网站的上线。本文将以上云操作流程为核心,结合阿里云的活动政策与用户系统梳理云服务器选购、域名注册、备案申请及域名绑定四大关键环节,以供用户完成线上业务部署做出参考。
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
10951 2
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
650 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
601 2
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
966 140
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1605 29
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。

推荐镜像

更多
  • DNS