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

举报
码农飞哥 发表于 2021/05/29 12:29:18 2021/05/29
【摘要】 前言 上一篇我们介绍了SpringSession中Session的保存过程,今天我们接着来看看Session的读取过程。相对保存过程,读取过程相对比较简单。 本文想从源码的角度,详细介绍一下Session的读取过程。 读取过程的时序图 如上,是读取Session的时序图,首先代码入口还是SessionRepositoryFilter过滤器的doFilterInt...

前言

上一篇我们介绍了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; } } //省略部分代码
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这个方法首先获取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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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

  1. 从cookie中获取sessionid的集合,可能cookie中存在多个sessionid。
  2. 循环sessionid的集合,分别根据sessionid到redis中获取session。
    获取sessionid是通过HttpSessionIdResolver接口的resolveSessionIds方法来实现的,SessionRepositoryFilter中定义了HttpSessionIdResolver接口的实例,其实现类是CookieHttpSessionIdResolver类。
	private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();

  
 
  • 1

所以,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);
	}

  
 
  • 1
  • 2
  • 3
  • 4

看到这个方法之后,我们发现这个方法只是一个中转方法,内部直接把请求交给了readCookieValues方法。同样的在CookieHttpSessionIdResolver类内部也定义了cookieSerializer这个属性,
它的实例对象是DefaultCookieSerializer。所以,真正的操作逻辑还是在DefaultCookieSerializer类中完成的。

	private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

  
 
  • 1

接下来,我们就来看看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;
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

如上,这个从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;
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

如上,我们可以看到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;
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

总结

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

文章来源: feige.blog.csdn.net,作者:码农飞哥,版权归原作者所有,如需转载,请联系作者。

原文链接:feige.blog.csdn.net/article/details/105583877

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。