欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringSession 請(qǐng)求與響應(yīng)重寫的實(shí)現(xiàn)

 更新時(shí)間:2018年11月25日 14:41:53   作者:glmapper  
這篇文章主要介紹了SpringSession 請(qǐng)求與響應(yīng)重寫的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧

我們知道, HttpServletRequsetHttpServletResponseServlet 標(biāo)準(zhǔn)所指定的 Java 語(yǔ)言與 Web 容器進(jìn)行交互的接口。接口本身只規(guī)定 java 語(yǔ)言對(duì) web 容器進(jìn)行訪問的行為方式,而具體的實(shí)現(xiàn)是由不同的 web 容器在其內(nèi)部實(shí)現(xiàn)的。

那么在運(yùn)行期,當(dāng)我們需要對(duì) HttpServletRequsetHttpServletResponse 的默認(rèn)實(shí)例進(jìn)行擴(kuò)展時(shí),我們就可以繼承 HttpServletRequestWrapperHttpServletResponseWrapper 來(lái)實(shí)現(xiàn)。

SpringSession 中因?yàn)槲覀円獙?shí)現(xiàn)不依賴容器本身的 getSession 實(shí)現(xiàn),因此需要擴(kuò)展 HttpServletRequset ,通過重寫 getSession 來(lái)實(shí)現(xiàn)分布式 session 的能力。下面就來(lái)看下 SpringSession 中對(duì)于 HttpServletRequset 的擴(kuò)展。

1、請(qǐng)求重寫

SpringSession 中對(duì)于請(qǐng)求重寫,在能力上主要體現(xiàn)在存儲(chǔ)方面,也就是 getSession 方法上。在 SessionRepositoryFilter 這個(gè)類中,是通過內(nèi)部類的方式實(shí)現(xiàn)了對(duì) HttpServletRequsetHttpServletResponse 的擴(kuò)展。

1.1 HttpServletRequset 擴(kuò)展實(shí)現(xiàn)

private final class SessionRepositoryRequestWrapper
			extends HttpServletRequestWrapper {
	// HttpServletResponse 實(shí)例
	private final HttpServletResponse response;
	// ServletContext 實(shí)例
	private final ServletContext servletContext;
 // requestedSession session對(duì)象
 private S requestedSession; 
 // 是否緩存 session
 private boolean requestedSessionCached;
	// sessionId
	private String requestedSessionId;
	// sessionId 是否有效
	private Boolean requestedSessionIdValid;
	// sessionId 是否失效
	private boolean requestedSessionInvalidated;
	
	// 省略方法
}

1.2 構(gòu)造方法

private SessionRepositoryRequestWrapper(HttpServletRequest request,
		HttpServletResponse response, ServletContext servletContext) {
	super(request);
	this.response = response;
	this.servletContext = servletContext;
}

構(gòu)造方法里面將 HttpServletRequest 、 HttpServletResponse 以及 ServletContext 實(shí)例傳遞進(jìn)來(lái),以便于后續(xù)擴(kuò)展使用。

1.3 getSession 方法

@Override
public HttpSessionWrapper getSession(boolean create) {
 // 從當(dāng)前請(qǐng)求線程中獲取 session
	HttpSessionWrapper currentSession = getCurrentSession();
	// 如果有直接返回
	if (currentSession != null) {
		return currentSession;
	}
	// 從請(qǐng)求中獲取 session,這里面會(huì)涉及到從緩存中拿session的過程
	S requestedSession = getRequestedSession();
	if (requestedSession != null) {
	 // 無(wú)效的會(huì)話id(不支持的會(huì)話存儲(chǔ)庫(kù))請(qǐng)求屬性名稱。
	 // 這里看下當(dāng)前的sessionId是否有效
		if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
		 // 設(shè)置當(dāng)前session的最后訪問時(shí)間,用于延遲session的有效期
			requestedSession.setLastAccessedTime(Instant.now());
			// 將requestedSessionIdValid置為true
			this.requestedSessionIdValid = true;
			// 包裝session
			currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
			// 不是新的session,如果是新的session則需要改變sessionId
			currentSession.setNew(false);
			// 將session設(shè)置到當(dāng)前請(qǐng)求上下文
			setCurrentSession(currentSession);
			// 返回session
			return currentSession;
		}
	}
	else {
		// 這里處理的是無(wú)效的sessionId的情況,但是當(dāng)前請(qǐng)求線程 session有效
		if (SESSION_LOGGER.isDebugEnabled()) {
			SESSION_LOGGER.debug(
					"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
		}
		// 將invalidSessionId置為true
		setAttribute(INVALID_SESSION_ID_ATTR, "true");
	}
	// 是否需要?jiǎng)?chuàng)建新的session
	if (!create) {
		return null;
	}
	if (SESSION_LOGGER.isDebugEnabled()) {
		SESSION_LOGGER.debug(
				"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
						+ SESSION_LOGGER_NAME,
				new RuntimeException(
						"For debugging purposes only (not an error)"));
	}
	// 創(chuàng)建新的session
	S session = SessionRepositoryFilter.this.sessionRepository.createSession();
	// 設(shè)置最后訪問時(shí)間,也就是指定了當(dāng)前session的有效期限
	session.setLastAccessedTime(Instant.now());
	// 包裝下當(dāng)前session
	currentSession = new HttpSessionWrapper(session, getServletContext());
	//設(shè)置到當(dāng)前請(qǐng)求線程
	setCurrentSession(currentSession);
	return currentSession;
}

上面這段代碼有幾個(gè)點(diǎn),這里單獨(dú)來(lái)解釋下。

getCurrentSession

這是為了在同一個(gè)請(qǐng)求過程中不需要重復(fù)的去從存儲(chǔ)中獲取session,在一個(gè)新的進(jìn)來(lái)時(shí),將當(dāng)前的 session 設(shè)置到當(dāng)前請(qǐng)求中,在后續(xù)處理過程如果需要getSession就不需要再去存儲(chǔ)介質(zhì)中再拿一次。

getRequestedSession

這個(gè)是根據(jù)請(qǐng)求信息去取 session ,這里面就包括了 sessionId 解析,從存儲(chǔ)獲取 session 對(duì)象等過程。

是否創(chuàng)建新的 session 對(duì)象

在當(dāng)前請(qǐng)求中和存儲(chǔ)中都沒有獲取到 session 信息的情況下,這里會(huì)根據(jù) create 參數(shù)來(lái)判斷是否創(chuàng)建新的 session 。這里一般用戶首次登錄時(shí)或者 session 失效時(shí)會(huì)走到。

1.4 getRequestedSession

根據(jù)請(qǐng)求信息來(lái)獲取 session 對(duì)象

private S getRequestedSession() {
 // 緩存的請(qǐng)求session是否存在
	if (!this.requestedSessionCached) {
  // 獲取 sessionId
  List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
  		.resolveSessionIds(this);
  // 通過sessionId來(lái)從存儲(chǔ)中獲取session
  for (String sessionId : sessionIds) {
  	if (this.requestedSessionId == null) {
  		this.requestedSessionId = sessionId;
  	}
  	S session = SessionRepositoryFilter.this.sessionRepository
  			.findById(sessionId);
  	if (session != null) {
  		this.requestedSession = session;
  		this.requestedSessionId = sessionId;
  		break;
  	}
  }
  this.requestedSessionCached = true;
	}
	return this.requestedSession;
}

這段代碼還是很有意思的,這里獲取 sessionId 返回的是個(gè)列表。當(dāng)然這里是 SpringSession 的實(shí)現(xiàn)策略,因?yàn)橹С?session ,所以這里以列表的形式返回的。OK,繼續(xù)來(lái)看如何解析 sessionId 的:

這里可以看到 SpringSession 對(duì)于 sessionId 獲取的兩種策略,一種是基于 cookie ,一種是基于 header ;分別來(lái)看下具體實(shí)現(xiàn)。

1.4.1 CookieHttpSessionIdResolver 獲取 sessionId

CookieHttpSessionIdResolver 中獲取 sessionId 的核心代碼如下:

 

其實(shí)這里沒啥好說(shuō)的,就是讀 cookie 。從 requestcookie 信息拿出來(lái),然后遍歷找當(dāng)前 sessionId 對(duì)應(yīng)的 cookie ,這里的判斷也很簡(jiǎn)單, 如果是以 SESSION 開頭,則表示是 SessionId ,畢竟 cookie 是共享的,不只有 sessionId,還有可能存儲(chǔ)其他內(nèi)容。

另外這里面有個(gè) jvmRoute,這個(gè)東西實(shí)際上很少能夠用到,因?yàn)榇蠖鄶?shù)情況下這個(gè)值都是null。這個(gè)我們?cè)诜治?CookieSerializer 時(shí)再來(lái)解釋。

1.4.2 HeaderHttpSessionIdResolver 獲取 sessionId

 

這個(gè)獲取更直接粗暴,就是根據(jù) headerNameheader中取值。

回到 getRequestedSession ,剩下的代碼中核心的都是和 sessionRepository 這個(gè)有關(guān)系,這部分就會(huì)涉及到存儲(chǔ)部分。不在本篇的分析范圍之內(nèi),會(huì)在存儲(chǔ)實(shí)現(xiàn)部分來(lái)分析。

1.5 HttpSessionWrapper

上面的代碼中當(dāng)我們拿到 session 實(shí)例是通常會(huì)包裝下,那么用到的就是這個(gè) HttpSessionWrapper 。

HttpSessionWrapper 繼承了 HttpSessionAdapter ,這個(gè) HttpSessionAdapter 就是將SpringSession 轉(zhuǎn)換成一個(gè)標(biāo)準(zhǔn) HttpSession 的適配類。 HttpSessionAdapter 實(shí)現(xiàn)了標(biāo)準(zhǔn) servlet 規(guī)范的 HttpSession 接口。

1.5.1 HttpSessionWrapper

HttpSessionWrapper 重寫了 invalidate 方法。從代碼來(lái)看,調(diào)用該方法產(chǎn)生的影響是:

  • requestedSessionInvalidated 置為 true ,標(biāo)識(shí)當(dāng)前 session 失效。
  • 將當(dāng)前請(qǐng)求中的 session 設(shè)置為 null ,那么在請(qǐng)求的后續(xù)調(diào)用中通過 getCurrentSession 將拿不到 session 信息。
  • 當(dāng)前緩存的 session 清楚,包括sessionId,session實(shí)例等。
  • 刪除存儲(chǔ)介質(zhì)中的session對(duì)象。

 1.5.2 HttpSessionAdapter

SpringSession 和標(biāo)準(zhǔn) HttpSession 的配置器類。這個(gè)怎么理解呢,來(lái)看下一段代碼:

@Override
public Object getAttribute(String name) {
	checkState();
	return this.session.getAttribute(name);
}

對(duì)于基于容器本身實(shí)現(xiàn)的 HttpSession 來(lái)說(shuō), getAttribute 的實(shí)現(xiàn)也是有容器本身決定。但是這里做了轉(zhuǎn)換之后, getAttribute 將會(huì)通過 SpringSession 中實(shí)現(xiàn)的方案來(lái)獲取。其他的 API 適配也是基于此實(shí)現(xiàn)。

SessionCommittingRequestDispatcher

實(shí)現(xiàn)了 RequestDispatcher 接口。關(guān)于 RequestDispatcher 可以參考這篇文章【Servlet】關(guān)于RequestDispatcher的原理 。 SessionCommittingRequestDispatcher 對(duì) forward 的行為并沒有改變。 對(duì)于 include 則是在 include 之前提交 session 。為什么這么做呢?

因?yàn)?include 方法使原先的 Servlet 和轉(zhuǎn)發(fā)到的 Servlet 都可以輸出響應(yīng)信息,即原先的 Servlet 還可以繼續(xù)輸出響應(yīng)信息;即請(qǐng)求轉(zhuǎn)發(fā)后,原先的 Servlet 還可以繼續(xù)輸出響應(yīng)信息,轉(zhuǎn)發(fā)到的 Servlet 對(duì)請(qǐng)求做出的響應(yīng)將并入原先 Servlet 的響應(yīng)對(duì)象中。

所以這個(gè)在 include 調(diào)用之前調(diào)用 commit ,這樣可以確保被包含的 Servlet 程序不能改變響應(yīng)消息的狀態(tài)碼和響應(yīng)頭。

2 響應(yīng)重寫

響應(yīng)重寫的目的是確保在請(qǐng)求提交時(shí)能夠把session保存起來(lái)。來(lái)看下 SessionRepositoryResponseWrapper 類的實(shí)現(xiàn):

 

這里面實(shí)現(xiàn)還就是重寫 onResponseCommitted ,也就是上面說(shuō)的,在請(qǐng)求提交時(shí)能夠通過這個(gè)回調(diào)函數(shù)將 session

保存到存儲(chǔ)容器中。

2.1 session 提交

最后來(lái)看下 commitSession

這個(gè)過程不會(huì)再去存儲(chǔ)容器中拿 session 信息,而是直接從當(dāng)前請(qǐng)求中拿。如果拿不到,則在回寫 cookie 時(shí)會(huì)將當(dāng)前 session 對(duì)應(yīng)的 cookie 值設(shè)置為空,這樣下次請(qǐng)求過來(lái)時(shí)攜帶的 sessionCookie 就是空,這樣就會(huì)重新觸發(fā)登陸。

如果拿到,則清空當(dāng)前請(qǐng)求中的 session 信息,然后將 session 保存到存儲(chǔ)容器中,并且將 sessionId 回寫到 cookie 中。

小結(jié)

本篇主要對(duì) SpringSession 中重寫 RequestResponse 進(jìn)行了分析。通過重寫 Request 請(qǐng)求來(lái)將 session 的存儲(chǔ)與存儲(chǔ)容器關(guān)聯(lián)起來(lái),通過重寫 Response 來(lái)處理 session 提交,將 session 保存到存儲(chǔ)容器中。

后面我們會(huì)繼續(xù)來(lái)分析 SpringSession 的源碼。最近也在學(xué)習(xí)鏈路跟蹤相關(guān)的技術(shù),也準(zhǔn)備寫一寫,有興趣的同學(xué)可以一起討論。 希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論