SpringSession的源码解析(生成session,保存session,写入cookie全流程分析)

Posted 飞飞1024

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSession的源码解析(生成session,保存session,写入cookie全流程分析)相关的知识,希望对你有一定的参考价值。


文章目录

前言

上一篇文章主要介绍了如何使用SpringSession,其实SpringSession的使用并不是很难,无非就是引入依赖,加下配置。但是,这仅仅只是知其然,要知其所以然,我们还是需要深入源码去理解。在看本文先我们先想想,下面这些问题Session是啥时候创建的呢?通过什么来创建的呢?创建之后如何保存到Redis?又是如何把SessionId设置到Cookie中的呢?带着这一系列的问题,今天就让我们来揭开SpringSession的神秘面纱,如果读者朋友们看完本文之后能够轻松的回答上面的问题,那本文的作用也就达到了。当然,如果您已经对这些知识了若指掌,那么就不需要看本文了。
看源码的过程真的是一个很枯燥乏味的过程,但是弄清楚了其调用过程之后,又是很让人兴奋的,话不多说,直接进入正题。

基础介绍

默认参数的设置

首先,我们从添加的SpringSession的配置类来看起,如下,是一段很基础的配置代码,就添加了​​@Configuration​​​注解和​​@EnableRedisHttpSession​​​注解。其中​​@Configuration​​​注解标注在类上,相当于把该类作为spring的xml配置文件中的​​<beans>​​​,作用为:配置spring容器(应用上下文),​​@EnableRedisHttpSession​​注解的作用是使SpringSession生效。

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = -1)
public class SessionConfig

点到EnableRedisHttpSession注解中,我们可以看到里面定义了RedisHttpSessionConfiguration的设置类,以及一些基础参数的设置,例如:session默认的失效时间,存入到redis的key的前缀名,这些我们参数我们在使用注解时都可以重新设置。例如:maxInactiveIntervalInSeconds设置为-1表示用不失效。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession
//默认最大的失效时间是30分钟
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS(1800秒);
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
//存入到redis的key的前缀名
public static final String DEFAULT_NAMESPACE = "spring:session";
String redisNamespace() default RedisOperationsSessionRepository.DEFAULT_NAMESPACE;

RedisHttpSessionConfiguration类是一个设置类,内部的作用主要是实例化RedisOperationsSessionRepository对象和RedisMessageListenerContainer对象等以及设置操作redis的工具类。

主要类的说明

类名

作用

RedisHttpSessionConfiguration

定义RedisOperationsSessionRepository等类的对象

SessionRepositoryFilter

过滤器,操作session的入口类

SessionRepositoryRequestWrapper

是SessionRepositoryFilter内部类,包装HttpRequest请求,调用RedisOperationsSessionRepository类相关的方法都是通过其完成

CookieHttpSessionIdResolver

这个类主要是调用DefaultCookieSerializer类的方法将sessionid存入cookie中,或者从cookie中读取sessionid,并返回给他的上一层

DefaultCookieSerializer

这个类是真正的操作cookie的类,设置cookie的相关属性,只需要重新实例化这个类即可

RedisOperationsSessionRepository

这个类的作用是生成session,并将session保存到redis中,另外就是根据sessionid查找session

RedisSession

这个类就是Spring Session的真正的实例对象,这是原始的session

上面我们对源码中一些重要的类做了个简单的介绍,下面我们就从源码的角度来看看其充当的作用。

操作session(生成session,保存session等过程)的时序图

首先,我们先看一下生成Session的调用时序图。
SpringSession的源码解析(生成session,保存session,写入cookie全流程分析)_spring

1. 调用的入口还是SessionRepositoryFilter类(PS:Spring是通过责任链的模式来执行每个过滤器的)的doFilterInternal方法。

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
//省略部分代码
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
try
//执行其他过滤器
filterChain.doFilter(wrappedRequest, wrappedResponse);

finally
//wrappedRequest是SessionRepositoryRequestWrapper类的一个实例
wrappedRequest.commitSession();

2. SessionRepositoryRequestWrapper类的getSession(true)方法

经过断点调试,并查看调用栈,发现调用这个​​filterChain.doFilter(wrappedRequest, wrappedResponse);​​​方法之后,最终会调用到SessionRepositoryRequestWrapper类的​​getSession(true)​​方法。其中,SessionRepositoryRequestWrapper类是SessionRepositoryFilter类的一个私有的不可被继承,被重写的内部类。

public HttpSessionWrapper getSession(boolean create) 
//1. 获取HttpSessionWrapper实例,如果可以获取到,则说明session已经生成了。就直接返回
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null)
return currentSession;

//如果可以获取到session
S requestedSession = getRequestedSession();
//如果HttpSessionWrapper实例为空,则需要将session对象封装到HttpSessionWrapper实例中,并设置到HttpRequestSerlvet中
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;


//如果获取不到session,则进入下面分支,创建session
else
//省略部分代码
//如果create为false,直接返回null
if (!create)
return null;

//省略部分代码
//如果create为true,则调用RedisOperationsSessionRepository类的createSession方法创建session实例
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;

如上代码所示:​​getSession(boolean create)​​ 方法主要有两块,1. 获取session实例,如果请求头中带着sessionid,则表示不是第一次请求,是可以获取到session的。2. 如果浏览器是第一次请求应用(没有sessionid)则获取不到session实例,需要创建session实例。在拿到生成的Session对象之后,紧接着会创建一个HttpSessionWrapper实例,并将前面生成的session传入其中,方便后面取用,然后将HttpSessionWrapper实例放入当前请求会话HttpServletRequest中,(Key是.CURRENT_SESSION,value是HttpSessionWrapper的实例)。

3. RedisOperationsSessionRepository类的​​createSession()​​方法

从前面的代码分析我们可以知道如果获取不到session实例,则会调用​​createSession()​​方法进行创建。这个方法是在RedisOperationsSessionRepository类中,该方法比较简单,主要就是实例化RedisSession对象。其中RedisSession对象中包括了sessionid,creationTime,maxInactiveInterval和lastAccessedTime等属性。其中原始的sessionid是一段唯一的UUID字符串。

@Override
public RedisSession createSession()
//实例化RedisSession对象
RedisSession redisSession = new RedisSession();
if (this.defaultMaxInactiveInterval != null)
//设置session的失效时间
redisSession.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));

return redisSession;


RedisSession()
this(new MapSession());
this.delta.put(CREATION_TIME_KEY, getCreationTime().toEpochMilli());
this.delta.put(MAX_INACTIVE_INTERVAL_KEY,
(int) getMaxInactiveInterval().getSeconds());
this.delta.put(LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
this.isNew = true;
this.flushImmediateIfNecessary();

另外,doFilterInternal方法在调用完其他方法之后,在finally代码块中会调用SessionRepositoryRequestWrapper类内部的commitSession()方法,而commitSession()方法会保存session信息到Redis中,并将sessionid写到cookie中。我们接着来看看commitSession()方法。

private void commitSession() 
//当前请求会话中获取HttpSessionWrapper对象的实例
HttpSessionWrapper wrappedSession = getCurrentSession();
//如果wrappedSession为空则调用expireSession写入一个空值的cookie
if (wrappedSession == null)
if (isInvalidateClientSession())
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this,
this.response);


else
//获取session
S session = wrappedSession.getSession();
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.SpringSession总结

SpringSession总结

Vue3 源码解析:代码生成器

flink作业提交源码解析 - StreamGraph的生成

flink作业提交源码解析 - StreamGraph的生成

Spark SQL源码解析Antlr4解析Sql并生成树