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

前言

上一篇我們介紹了SpringSession中Session的保存過(guò)程,今天我們接著來(lái)看看Session的讀取過(guò)程。相對(duì)保存過(guò)程,讀取過(guò)程相對(duì)比較簡(jiǎn)單。
本文想從源碼的角度,詳細(xì)介紹一下Session的讀取過(guò)程。

讀取過(guò)程的時(shí)序圖

在這里插入圖片描述
如上,是讀取Session的時(shí)序圖,首先代碼入口還是SessionRepositoryFilter過(guò)濾器的doFilterInternal方法。這個(gè)方法里還是會(huì)調(diào)用到SessionRepositoryRequestWrapper類的getSession()方法,這個(gè)getSession方法是讀取Session的開(kāi)始,這個(gè)方法內(nèi)部會(huì)調(diào)用getSession(true)方法。那我們就從SessionRepositoryRequestWrapper類的getSession(true)方法開(kāi)始說(shuō)起。

getSession(true)方法。

@Override
		public HttpSessionWrapper getSession(boolean create) {
			//獲取HttpSessionWrapper類,這個(gè)類會(huì)包裝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;
				}
			}
			//省略部分代碼
	}

這個(gè)方法首先獲取HttpSessionWrapper對(duì)象,這個(gè)對(duì)象的作用是用于封裝session,返回給其上一層,如果可以獲取到則說(shuō)明Session信息已經(jīng)拿到了,就直接返回。
如果獲取不到則調(diào)用getRequestedSession()方法。這個(gè)方法就是獲取session的主方法。接著讓我們來(lái)看看這個(gè)方法吧。

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;
					}
					//根據(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;
		}

如上,這個(gè)方法主要有兩步:

  1. 從cookie中獲取sessionid的集合,可能cookie中存在多個(gè)sessionid。
  2. 循環(huán)sessionid的集合,分別根據(jù)sessionid到redis中獲取session。
    獲取sessionid是通過(guò)HttpSessionIdResolver接口的resolveSessionIds方法來(lái)實(shí)現(xiàn)的,SessionRepositoryFilter中定義了HttpSessionIdResolver接口的實(shí)例,其實(shí)現(xiàn)類是CookieHttpSessionIdResolver類。
	private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();

所以,SessionRepositoryFilter.this.httpSessionIdResolver的實(shí)例是一個(gè)CookieHttpSessionIdResolver對(duì)象。
SessionRepositoryFilter.this.sessionRepository的實(shí)例是一個(gè)RedisOperationsSessionRepository對(duì)象。
那么接下來(lái)我們就分別來(lái)看看這個(gè)兩個(gè)類的相關(guān)方法。

resolveSessionIds方法

接下來(lái),我們就來(lái)到了CookieHttpSessionIdResolver類的resolveSessionIds方法,這個(gè)方法主要的作用就是從cookie中獲取sessionid。

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

看到這個(gè)方法之后,我們發(fā)現(xiàn)這個(gè)方法只是一個(gè)中轉(zhuǎn)方法,內(nèi)部直接把請(qǐng)求交給了readCookieValues方法。同樣的在CookieHttpSessionIdResolver類內(nèi)部也定義了cookieSerializer這個(gè)屬性,
它的實(shí)例對(duì)象是DefaultCookieSerializer。所以,真正的操作邏輯還是在DefaultCookieSerializer類中完成的。

	private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

接下來(lái),我們就來(lái)看看DefaultCookieSerializer這個(gè)類的的readCookieValues方法。

readCookieValues方法

	@Override
	public List<String> readCookieValues(HttpServletRequest request) {
		//從請(qǐng)求頭中獲取cookies
		Cookie[] cookies = request.getCookies();
		List<String> matchingCookieValues = new ArrayList<>();
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				//獲取存放sessionid的那個(gè)cookie,cookieName默認(rèn)是SESSION
				if (this.cookieName.equals(cookie.getName())) {
					//默認(rèn)的話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;
	}

如上,這個(gè)從cookie中獲取sessionid的方法也很簡(jiǎn)單,無(wú)非就是從當(dāng)前的HttpServletRequest對(duì)象中獲取所有的cookie,然后,提取name等于cookieName的cookie值。
這個(gè)cookie值就是sessionid。

findById方法

從cookie中那個(gè)sessionid之后會(huì)調(diào)用RedisOperationsSessionRepository類的findById方法,這個(gè)方法的作用就是從redis中獲取保存的session信息。

		public RedisSession findById(String id) {
		//直接調(diào)用getSession方法
		return getSession(id, false);
	}

	
	private RedisSession getSession(String id, boolean allowExpired) {
		//獲取當(dāng)前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在轉(zhuǎn)成RedisSession,并最終返回
		RedisSession result = new RedisSession(loaded);
		result.originalLastAccessTime = loaded.getLastAccessedTime();
		return result;
	}

如上,我們可以看到findById方法內(nèi)部直接調(diào)用了getSession方法,所以,所有的邏輯都在這個(gè)方法,而這個(gè)方法的邏輯分為三步:

  1. 根據(jù)sessionid獲取當(dāng)前session在redis保存的所有數(shù)據(jù)
  2. 傳入數(shù)據(jù)并組裝成MapSession
  3. 將MapSession在轉(zhuǎn)成RedisSession,并最終返回
    我們一步步的看
    首先,第一步根據(jù)sessionid獲取當(dāng)前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中的值不是字符類型的。而是通過(guò)對(duì)象保存的,是hash類型。

總結(jié)

至此,從Cookie中讀取SessionId,然后,根據(jù)SessionId查詢保存到Redis中的數(shù)據(jù)的全過(guò)程,希望對(duì)大家有所幫助。




作者:碼農(nóng)飛哥

微信公眾號(hào):碼農(nóng)飛哥