1. 程式人生 > >Spring Session 原始碼分析(1)——springSessionRepositoryFilter

Spring Session 原始碼分析(1)——springSessionRepositoryFilter

#Tomcat Session
對於session 是一個老生暢談的話題了,Session管理是JavaEE容器比較重要的一部分,
Tomcat中主要由每個context容器內的一個Manager物件來管理session。對於這個manager物件的實現,可以根據tomcat提供的介面或基類來自己定製,同時,tomcat也提供了標準實現。
在每個context物件,即web app都具有一個獨立的manager物件。通過server.xml可以配置定製化的manager,也可以不配置。不管怎樣,在生成context物件時,都會生成一個manager物件。預設的是StandardManager類,在Tomcat 中,Session 是儲存到一個ConcurrentHashMap 中的。

/**
 * Minimal implementation of the <b>Manager</b> interface that supports
 * no session persistence or distributable capabilities.  This class may
 * be subclassed to create more sophisticated Manager implementations.
 *
 * @author Craig R. McClanahan
 */
public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
    /**
     * The set of currently active Sessions for this Manager, keyed by
     * session identifier.
     */
    protected Map<String, Session> sessions = new ConcurrentHashMap<>();
    ...
 }

Session 自定義化,可以實現標準servlet的session介面:

javax.servlet.http.HttpSession

Tomcat也提供了標準的session實現:

org.apache.catalina.session.StandardSession
/**
 * Standard implementation of the <b>Session</b> interface.  This object is
 * serializable, so that it can be stored in persistent storage or transferred
 * to a different JVM for distributable session support.
 * <p>
 * <b>IMPLEMENTATION NOTE</b>:  An instance of this class represents both the
 * internal (Session) and application level (HttpSession) view of the session.
 * However, because the class itself is not declared public, Java logic outside
 * of the <code>org.apache.catalina.session</code> package cannot cast an
 * HttpSession view of this instance back to a Session view.
 * <p>
 * <b>IMPLEMENTATION NOTE</b>:  If you add fields to this class, you must
 * make sure that you carry them over in the read/writeObject methods so
 * that this class is properly serialized.
 *
 * @author Craig R. McClanahan
 * @author Sean Legassick
 * @author <a href="mailto:
[email protected]
">Jon S. Stevens</a> */ public class StandardSession implements HttpSession, Session, Serializable { /** * Construct a new Session associated with the specified Manager. * * @param manager The manager with which this Session is associated */ public StandardSession(Manager manager) { super(); this.manager = manager; // Initialize access count if (ACTIVITY_CHECK) { accessCount = new AtomicInteger(); } } ... }

今天的目的不是討論Tomcat 對session的管理,這裡只是做個引子,方便感興趣的朋友深入瞭解,今天我們主要看Spring 如果接管 Session的,並且使用Redis作為Session的儲存容器。

#Spring Session

這篇文章並不是深入剖析Spring Session的結構,主要是一起來看看Spring 是如何接管Session的,從中學習一些架構思想。

##Spring Session 基於XML的配置

注意:下面的配置是核心配置,而非全部配置

spring 配置檔案:

<bean id="redisHttpSessionConfiguration"
    class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
	<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>

web.xml:

<filter>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

##DelegatingFilterProxy

DelegatingFilterProxy 是一個Filter的代理類,DelegatingFilterProxy 繼承自 GenericFilterBean,GenericFilterBean是一個抽象類,分別實現了 Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean介面,繼承關係如下圖:

這裡寫圖片描述

對於Filter 來說,最重要肯定就是初始化(init)和doFilter 方法了,初始化方法在父類GenericFilterBean 中:

/**
 * Standard way of initializing this filter.
 * Map config parameters onto bean properties of this filter, and
 * invoke subclass initialization.
 * @param filterConfig the configuration for this filter
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 * @see #initFilterBean
 */
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
	...//省略部分程式碼

	// Let subclasses do whatever initialization they like.
	initFilterBean();  //注意這裡

	if (logger.isDebugEnabled()) {
		logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
	}
}

initFilterBean在子類中實現,也就是說當DelegatingFilterProxy 在執行Filter的 init 方法時,會呼叫 initFilterBean方法,如下:

	@Override
	protected void initFilterBean() throws ServletException {
		synchronized (this.delegateMonitor) {
		    // delegate 為實際的Filter
			if (this.delegate == null) {
				// If no target bean name specified, use filter name.
				if (this.targetBeanName == null) {
				    //這裡的targetBeanName  便是我們web.xml 中配置的springSessionRepositoryFilter
					this.targetBeanName = getFilterName();
				}
				// Fetch Spring root application context and initialize the delegate early,
				// if possible. If the root application context will be started after this
				// filter proxy, we'll have to resort to lazy initialization.
				WebApplicationContext wac = findWebApplicationContext();
				if (wac != null) {
				    //初始化Filter
					this.delegate = initDelegate(wac);
				}
			}
		}
	}

delegate 為真正的Filter,通過web.xml 中配置的Filter 名字(即:springSessionRepositoryFilter)來獲取這個Filter,我們繼續往下看initDelegate 這個方法:

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
	Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
	if (isTargetFilterLifecycle()) {
		delegate.init(getFilterConfig());
	}
	return delegate;
}

通過上下文,獲取到這個Filter Bean,然後初始化這個Filter。但是我們似乎沒有手動配置這個Bean,那麼這個切入點就是我們開始在spring 配置檔案中宣告的 RedisHttpSessionConfiguration 這個bean了。
這裡寫圖片描述

##SessionRepositoryFilter
Spring Session 在 RedisHttpSessionConfiguration以及它的父類 SpringHttpSessionConfiguration中 自動生成了許多Bean,這裡我只列舉了springSessionRepositoryFilter 這個Bean,而這個Bean 是SessionRepositoryFilter 型別的。

@Configuration
public class SpringHttpSessionConfiguration {

private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();

private HttpSessionStrategy httpSessionStrategy = this.defaultHttpSessionStrategy;

@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
		SessionRepository<S> sessionRepository) {
		//傳入 sessionRepository
	SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(
			sessionRepository);
	sessionRepositoryFilter.setServletContext(this.servletContext);
	if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
		sessionRepositoryFilter.setHttpSessionStrategy(
				(MultiHttpSessionStrategy) this.httpSessionStrategy);
	}
	else {
		sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
	}
	return sessionRepositoryFilter;
}

##RedisOperationsSessionRepository
現在我們知道,DelegatingFilterProxy 中真正的Filter是SessionRepositoryFilter,在生成SessionRepositoryFilter 是傳入了sessionRepository,通過名字我們大概知道,這個應該是具體操作session的類,而這個類又具體是什麼呢?,看下面的程式碼:

@Bean
public RedisOperationsSessionRepository sessionRepository(
		@Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
		ApplicationEventPublisher applicationEventPublisher) {
	RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
			sessionRedisTemplate);
	sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
	sessionRepository
			.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
	if (this.defaultRedisSerializer != null) {
		sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
	}

	String redisNamespace = getRedisNamespace();
	if (StringUtils.hasText(redisNamespace)) {
		sessionRepository.setRedisKeyNamespace(redisNamespace);
	}

	sessionRepository.setRedisFlushMode(this.redisFlushMode);
	return sessionRepository;
}

到這裡我們知道,這個sessionRepository 具體就是:RedisOperationsSessionRepository ,這個類就是實際通過redis 操作session的類,這裡就不展開了,後面會簡單看一下。

好了,現在回到 DelegatingFilterProxy -> initFilterBean 方法,具體的Filter 已經找到了,這個Filter 就是SessionRepositoryFilter,那麼這個Filter 又是在什麼時候生效的呢?,接下來我們看 DelegatingFilterProxy 的doFilter 方法。

DelegatingFilterProxy  -> doFilter:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

	// Lazily initialize the delegate if necessary.
	Filter delegateToUse = this.delegate;
	if (delegateToUse == null) {
		synchronized (this.delegateMonitor) {
			delegateToUse = this.delegate;
			if (delegateToUse == null) {
				WebApplicationContext wac = findWebApplicationContext();
				if (wac == null) {
					throw new IllegalStateException("No WebApplicationContext found: " +
							"no ContextLoaderListener or DispatcherServlet registered?");
				}
				delegateToUse = initDelegate(wac);
			}
			this.delegate = delegateToUse;
		}
	}

	// Let the delegate perform the actual doFilter operation.
	invokeDelegate(delegateToUse, request, response, filterChain);
}

注意 invokeDelegate 方法,進去看看:

protected void invokeDelegate(
		Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {
    //呼叫 delegate的 doFilter 方法
	delegate.doFilter(request, response, filterChain);
}

這裡我們大概知道了,DelegatingFilterProxy 實際上是委託給delegate 的,而這裡的delegate 是SessionRepositoryFilter,現在我們又回到 SessionRepositoryFilter看看:

這裡寫圖片描述

SessionRepositoryFilter 沒有重寫 doFilter 方法,因此檢視父類:OncePerRequestFilter,通過名字我們知道,這個Filter 只執行一次

OncePerRequestFilter -> doFilter:
/**
 * This {@code doFilter} implementation stores a request attribute for
 * "already filtered", proceeding without filtering again if the attribute is already
 * there.
 */
public final void doFilter(ServletRequest request, ServletResponse response,
		FilterChain filterChain) throws ServletException, IOException {

	if (!(request instanceof HttpServletRequest)
			|| !(response instanceof HttpServletResponse)) {
		throw new ServletException(
				"OncePerRequestFilter just supports HTTP requests");
	}
	HttpServletRequest httpRequest = (HttpServletRequest) request;
	HttpServletResponse httpResponse = (HttpServletResponse) response;
	//檢視是否已經過濾了
	boolean hasAlreadyFilteredAttribute = request
			.getAttribute(this.alreadyFilteredAttributeName) != null;

	if (hasAlreadyFilteredAttribute) {

		// Proceed without invoking this filter...
		filterChain.doFilter(request, response);
	}
	else {
		// Do invoke this filter...
		//加入已經過濾標誌
		request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
		try {
		    //呼叫 doFilterInternal
			doFilterInternal(httpRequest, httpResponse, filterChain);
		}
		finally {
			// Remove the "already filtered" request attribute for this request.
			request.removeAttribute(this.alreadyFilteredAttributeName);
		}
	}
}

在該方法中,會檢查該請求是否已經被過濾了,如果過濾過了那麼執行下一個Filter,否則放入已過濾標識,然後執行Filter 功能,doFilterInternal 在SessionRepositoryFilter 被重寫

SessionRepositoryFilter -> doFilterInternal:
@Override
protected void doFilterInternal(HttpServletRequest request,
		HttpServletResponse response, FilterChain filterChain)
				throws ServletException, IOException {
	request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

	SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
			request, response, this.servletContext);
	SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
			wrappedRequest, response);
    //包裝 request,response
	HttpServletRequest strategyRequest = this.httpSessionStrategy
			.wrapRequest(wrappedRequest, wrappedResponse);
	HttpServletResponse strategyResponse = this.httpSessionStrategy
			.wrapResponse(wrappedRequest, wrappedResponse);

	try {
		filterChain.doFilter(strategyRequest, strategyResponse);
	}
	finally {
	    //更新session
		wrappedRequest.commitSession();
	}
}

Spring 接管Tomcat 的session 主要在這裡可以看出來,Spring會包裝HttpServletRequest ,HttpServletResponse ,當執行了這個Filter後,將包裝了 request,response 專遞到後面,這樣後面所使用的都是spring 包裝過的,這樣在獲取session的介面,就被spring 所控制了,當由伺服器返回給使用者是,呼叫commitSession,更新session。
具接下來我們看一下SessionRepositoryRequestWrapper 這個類:
這裡寫圖片描述

private S getSession(String sessionId) {
    //通過sessionRepository 獲取session,在redis 中 這裡的 sessionRepository 是RedisOperationsSessionRepository 
	S session = SessionRepositoryFilter.this.sessionRepository
			.getSession(sessionId);
	if (session == null) {
		return null;
	}
	session.setLastAccessedTime(System.currentTimeMillis());
	return session;
}

// 重寫 父類 getSession 方法
@Override
public HttpSessionWrapper getSession(boolean create) {
	HttpSessionWrapper currentSession = getCurrentSession();
	if (currentSession != null) {
		return currentSession;
	}
	//從當前請求獲取sessionId
	String requestedSessionId = getRequestedSessionId();
	if (requestedSessionId != null
			&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {
		S session = getSession(requestedSessionId);
		if (session != null) {
			this.requestedSessionIdValid = true;
			//對Spring session 進行包裝(包裝成HttpSession)
			currentSession = new HttpSessionWrapper(session, getServletContext());
			currentSession.setNew(false);
			setCurrentSession(currentSession);
			return currentSession;
		}
		else {
			// This is an invalid session id. No need to ask again if
			// request.getSession is invoked for the duration of this request
			setAttribute(INVALID_SESSION_ID_ATTR, "true");
		}
	}
	if (!create) {
		return null;
	}
	S session = SessionRepositoryFilter.this.sessionRepository.createSession();
	session.setLastAccessedTime(System.currentTimeMillis());
	//對Spring session 進行包裝(包裝成HttpSession)
	currentSession = new HttpSessionWrapper(session, getServletContext());
	setCurrentSession(currentSession);
	return currentSession;
}

到這裡應該就很清楚了,對HttpServletRequest 進行包裝,然後重寫對Session操作的介面,內部呼叫 SessionRepository 的實現類來對session 進行操作,ok,這下終於明白Spring Session 是如何控制session的了,最後進行總結一下:
當我們配置 DelegatingFilterProxy 時,會配置 filter-name:springSessionRepositoryFilter,當我們配置 RedisHttpSessionConfiguration 這個bean時,這個Filter 則由Spring 給我生成,而這個Filter 實際是 :SessionRepositoryFilter, 當有請求到達時,DelegatingFilterProxy 委託給 SessionRepositoryFilter,而它又將HttpServletRequest,HttpServletResponse 進行一定的包裝,重寫對session操作的介面,然後將包裝好的request,response 傳遞到後續的Filter中,完成了對Session的攔截操作,後續應用操作的Session 都是Spring Session 包裝後的Session。

相關推薦

Spring Session 原始碼分析1——springSessionRepositoryFilter

#Tomcat Session 對於session 是一個老生暢談的話題了,Session管理是JavaEE容器比較重要的一部分, Tomcat中主要由每個context容器內的一個Manager物件來管理session。對於這個manager物件的實現,可以

spring IOC原始碼分析1

1.何謂Spring IOC         何謂Spring IOC?書上謂之“依賴注入”,那何謂“依賴注入”?         作為一個Java程式猿,應該遇到過這樣的問題,當你在程式碼中需要使用某個類提供的功能時,你首先需要new一個物件,給它傳遞必要的引數,然後才

Spring初始化過程原始碼分析1

本文主要詳細分析Spring初始化過程的原始碼分析,目的是理解Spring具體是如何工作的。部分內容查閱於網路,有不妥之處望指正。 1、web專案中伺服器一啟動就開始載入web.xml,Spring的啟動是從web.xml中的org.springframewo

Mybatis原始碼分析1—— Mapper檔案解析

感覺CSDN對markdown的支援不夠友好,總是伴隨各種問題,很惱火! xxMapper.xml的解析主要由XMLMapperBuilder類完成,parse方法來完成解析: public void parse() { if (!configuration.isRes

比特幣BTC原始碼分析1:地址生成過程

一、生成一個比特幣錢地址 二、根據原始碼整理比特幣地址生成過程 1、取得公鑰PubKey 2、使用 RIPEMD160(SHA256(PubKey)) 雜湊演算法,取公鑰並對其雜湊兩次 3、給雜湊加上地址生成演算法版本的字首 4、對於第二步生成的結果,使用SHA256(SHA256

以太坊ETH原始碼分析1:地址生成過程

一、生成一個以太坊錢包地址 通過以太坊命令列客戶端geth可以很簡單的獲得一個以太坊地址,如下: ~/go/src/github.com/ethereum/go-ethereum/build/bin$geth account new INFO [11-03|20:09:33.219]

jdk原始碼分析1java.lang.Object

java.lang.Object原始碼分析 public final native Class<?> getClass() public native int hashCode(); public boolean e

tensorflow原始碼分析1

variable類:        通過例項化Variable類可以新增一個變數到graph,在使用變數之前必須對變數顯示的初始化,初始化可以使用assign為變數賦值也可以通過變數本身的initializer方法。     &nb

ES5.6.2原始碼分析1:準備工作

1、gradle安裝 下載4.5版本,解壓後配置環境變數即可。 注:gradle安裝完成後, 為了加快依賴檔案的下載需要在使用者目錄中新建init.gradle檔案(讓全域性可見,build時會用到)。檔案的具體內容為: 目錄:C:\Users\admin.gradle

tensorflowV1.11-原始碼分析1

##</Users/deepmyhaspl/docs/tensorflow-src/tensorflow-r1.11>####[4]|<====configure.py=====>|## # Copyright 2017 The TensorFlow Authors. All

Django rest framework原始碼分析1----認證

目錄 一、基礎 1.1.安裝 兩種方式: pip install djangorestframework 1.2.需要先了解的一些知識 理解下面兩個知識點非常重要,django-rest-framework原始碼中到處都是基於CBV和麵向物件的封裝 (1)面向物件封裝的兩大特性

Android6.0的Looper原始碼分析1

Android在Java標準執行緒模型的基礎上,提供了訊息驅動機制,用於多執行緒之間的通訊。而其具體實現就是Looper。 Android Looper的實現主要包括了3個概念:Message,MessageQueue,Handler,Looper。其中Message就是

libevent原始碼分析1

有過看nginx原始碼的基礎,現在來看libevent原始碼,感覺要輕鬆多了。。 第一篇文章,主要是還是介紹一些幾個重要的資料結構吧。。。。 首先是event結構:struct event { TAILQ_ENTRY (event) ev_next; //用於構成eve

Freescale i.MX6 Linux Ethernet Driver驅動原始碼分析1

最近需要在Freescale i.MX6上移植Ethernet AVB的核心patch,Ethernet AVB的Wiki:http://en.wikipedia.org/wiki/Audio_Video_Bridging,而Freescale原來已經在kernel 3.

Android系統原理與原始碼分析1:利用Java反射技術阻止通過按鈕關閉對話方塊

本文為原創,如需轉載,請註明作者和出處,謝謝!     眾所周知,AlertDialog類用於顯示對話方塊。關於AlertDialog的基本用法在這裡就不詳細介紹了,網上有很多,讀者可以自己搜尋。那

支援向量機—SMO演算法原始碼分析1

支援向量機的理論支援在此不細說,可以參考李航的《統計學習》,還有西瓜書。 簡化版SMO演算法處理小規模資料集 SMO演算法是一種啟發式演算法。此簡化版首先在資料集上遍歷每一個alpha,然後在剩下的alpha集合中隨機選擇另一個alpha,從而建立alpha

Weka原始碼分析1逆向工程Eclipse外掛ObjectAid和AmaterasUML的安裝方法

    為了更好的分析Weka原始碼中各個類之間的關係,需要根據.java檔案將各個類之間的關係以UML中的類圖(Class diagram)的形式展示出來。在眾多可以實現逆向工程的Eclipse UML外掛中,我覺得AmaterasUML和ObjectAid是相對比較理想

yii1.1核心原始碼分析1目錄結構說明

framework框架核心庫 1.base底層類庫資料夾包括CApplication:(應用類,負責全域性的使用者請求處理,它管理的應用元件集,將提供特定功能給整個應用程式);CComponent(元

Spring Cloud技術分析1——服務治理

地址:http://tech.lede.com/    本文作為系列的第一篇正文,從Spring Cloud中的核心專案Spring Cloud Netflix入手,闡述了Spring Cloud Netflix的優勢,介紹了Spring Cloud Netflix進行服務治

libevent原始碼分析1--2.1.8--標誌資訊

一、事件型別  event-internal.h /** * @name event flags * * Flags to pass to event_new(), event_assign(), event_pending(), and * anything e