他来了,读取SpringSession中读取session的过程

他来了,读取SpringSession中读取session的过程

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

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

读取过程的时序图在这里插入图片描述

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

getSession(true)方法。代码语言:javascript代码运行次数:0运行复制@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()方法代码语言:javascript代码运行次数:0运行复制private S getRequestedSession() {

if (!this.requestedSessionCached) {

//从cookie中获取sessionid集合

List 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;

}

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

从cookie中获取sessionid的集合,可能cookie中存在多个sessionid。循环sessionid的集合,分别根据sessionid到redis中获取session。

获取sessionid是通过HttpSessionIdResolver接口的resolveSessionIds方法来实现的,SessionRepositoryFilter中定义了HttpSessionIdResolver接口的实例,其实现类是CookieHttpSessionIdResolver类。代码语言:javascript代码运行次数:0运行复制 private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();

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

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

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

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

代码语言:javascript代码运行次数:0运行复制 @Override

public List resolveSessionIds(HttpServletRequest request) {

return this.cookieSerializer.readCookieValues(request);

}

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

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

代码语言:javascript代码运行次数:0运行复制 private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

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

readCookieValues方法代码语言:javascript代码运行次数:0运行复制 @Override

public List readCookieValues(HttpServletRequest request) {

//从请求头中获取cookies

Cookie[] cookies = request.getCookies();

List 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信息。

代码语言:javascript代码运行次数:0运行复制 public RedisSession findById(String id) {

//直接调用getSession方法

return getSession(id, false);

}

private RedisSession getSession(String id, boolean allowExpired) {

//获取当前session在redis保存的所有数据

Map 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方法,所以,所有的逻辑都在这个方法,而这个方法的逻辑分为三步:

根据sessionid获取当前session在redis保存的所有数据传入数据并组装成MapSession将MapSession转成RedisSession,并最终返回

我们一步步的看

首先,第一步根据sessionid获取当前session在redis保存的所有数据代码语言:javascript代码运行次数:0运行复制 private BoundHashOperations 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中的数据的全过程,希望对大家有所帮助。

相关文章

Win10/11注册表中添加或删除开机自启项
beat365官方网站登录

Win10/11注册表中添加或删除开机自启项

📅 07-02 👁️ 7650
《奶块》魔法羊毛图鉴介绍
beat365官方网站登录

《奶块》魔法羊毛图鉴介绍

📅 07-03 👁️ 7913
解决U盘不显示容量问题的方法(教你如何修复U盘显示容量异常的情况)