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原始碼分析(1)java.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