SpringSession的源碼解析(從Cookie中讀取Sessionid,根據(jù)sessionid查詢信息全流程分析)

前言

上一篇我們介紹了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 sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
.resolveSessionIds(this);
//遍歷sessionid集合,分別獲取HttpSession
for (String sessionId : sessionIds) {
if (this.requestedSessionId == null) {
this.requestedSessionId = sessionId;
}
//根據(jù)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。
循環(huán)sessionid的集合,分別根據(jù)sessionid到redis中獲取session。
獲取sessionid是通過HttpSessionIdResolver接口的resolveSessionIds方法來實現(xiàn)的,SessionRepositoryFilter中定義了HttpSessionIdResolver接口的實例,其實現(xiàn)類是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);
}

看到這個方法之后,我們發(fā)現(xiàn)這個方法只是一個中轉方法,內部直接把請求交給了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保存的所有數(shù)據(jù)
	Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
	if (entries.isEmpty()) {
		return null;
	}
	//傳入數(shù)據(jù)并組裝成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方法,所以,所有的邏輯都在這個方法,而這個方法的邏輯分為三步:

根據(jù)sessionid獲取當前session在redis保存的所有數(shù)據(jù)
傳入數(shù)據(jù)并組裝成MapSession
將MapSession在轉成RedisSession,并最終返回
我們一步步的看
首先,第一步根據(jù)sessionid獲取當前session在redis保存的所有數(shù)據(jù)

private BoundHashOperations<Object, Object, Object> getSessionBoundHashOperations(
		String sessionId) {
	//拿到key
	String key = getSessionKey(sessionId);
	//根據(jù)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,然后,根據(jù)SessionId查詢保存到Redis中的數(shù)據(jù)的全過程,希望對大家有所幫助。





作者:碼農飛哥
微信公眾號:碼農飛哥