Spring Session的架構(gòu)
Spring Session定義了一組標(biāo)準(zhǔn)的接口,可以通過實(shí)現(xiàn)這些接口間接訪問底層的數(shù)據(jù)存儲(chǔ)。Spring Session定義了如下核心接口:Session、ExpiringSession以及SessionRepository,針對不同的數(shù)據(jù)存儲(chǔ),它們需要分別實(shí)現(xiàn)。
- org.springframework.session.Session接口定義了session的基本功能,如設(shè)置和移除屬性。這個(gè)接口并不關(guān)心底層技術(shù),因此能夠比servlet HttpSession適用于更為廣泛的場景中。
- org.springframework.session.ExpiringSession擴(kuò)展了Session接口,它提供了判斷session是否過期的屬性。RedisSession是這個(gè)接口的一個(gè)樣例實(shí)現(xiàn)。
- org.springframework.session.SessionRepository定義了創(chuàng)建、保存、刪除以及檢索session的方法。將Session實(shí)例真正保存到數(shù)據(jù)存儲(chǔ)的邏輯是在這個(gè)接口的實(shí)現(xiàn)中編碼完成的。例如,RedisOperationsSessionRepository就是這個(gè)接口的一個(gè)實(shí)現(xiàn),它會(huì)在Redis中創(chuàng)建、存儲(chǔ)和刪除session。
在請求/響應(yīng)周期中,客戶端和服務(wù)器之間需要協(xié)商同意一種傳遞session id的方式。例如,如果請求是通過HTTP傳遞進(jìn)來的,那么session可以通過HTTP cookie或HTTP Header信息與請求進(jìn)行關(guān)聯(lián)。
對于HTTP協(xié)議來說,Spring Session定義了HttpSessionStrategy接口以及兩個(gè)默認(rèn)實(shí)現(xiàn),即CookieHttpSessionStrategy和HeaderHttpSessionStrategy,其中前者使用HTTP cookie將請求與session id關(guān)聯(lián),而后者使用HTTP header將請求與session關(guān)聯(lián)。
核心思想:
通過 org.springframework.session.web.http.SessionRepositoryFilter 的doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)對所有的請求進(jìn)行攔截,使用包裝(Wrapper)或者說是裝飾(Decorator)模式對 request, response進(jìn)行包裝并重寫HttpServletRequest 的 getSession方法,然后通過 filterChain向后傳遞。
** 本文基于 Spring Session 1.3.0.RELEASE 源代碼分析**
首先,看看我們在web.xml中配置:
<!-- spring session -->
<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 顧名思義是一個(gè)Filter的代理類,其代碼如下:
public class DelegatingFilterProxy extends GenericFilterBean {
private WebApplicationContext webApplicationContext;
private String targetBeanName;
private boolean targetFilterLifecycle = false;
private volatile Filter delegate;
private final Object delegateMonitor = new Object();
public DelegatingFilterProxy() {
}
public DelegatingFilterProxy(Filter delegate) {
Assert.notNull(delegate, "delegate Filter object must not be null");
this.delegate = delegate;
}
public DelegatingFilterProxy(String targetBeanName) {
this(targetBeanName, null);
}
public DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) {
Assert.hasText(targetBeanName, "target Filter bean name must not be null or empty");
this.setTargetBeanName(targetBeanName);
this.webApplicationContext = wac;
if (wac != null) {
this.setEnvironment(wac.getEnvironment());
}
}
}
DelegatingFilterProxy 繼承自 GenericFilterBean,GenericFilterBean是一個(gè)抽象類,分別實(shí)現(xiàn)了 Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean接口,繼承關(guān)系如下圖:

GenericFilterBean 主要代碼如下:
public abstract class GenericFilterBean implements
Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws ServletException {
initFilterBean();
}
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
try {
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
// Let subclasses do whatever initialization they like.
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
protected void initFilterBean() throws ServletException {
}
}
由此可見,當(dāng)DelegatingFilterProxy 在執(zhí)行Filter的 init 方法時(shí),會(huì)調(diào)用 initFilterBean方法,如下:
/**
* Spring容器啟動(dòng)時(shí)初始化Filter
*/
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// 如果targetBeanName為null,則使用當(dāng)前Filter的名稱
if (this.targetBeanName == null) {
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) {
this.delegate = initDelegate(wac);
}
}
}
}
getFilterName方法繼承自GenericFilterBean ,如下:
public final FilterConfig getFilterConfig() {
return this.filterConfig;
}
protected final String getFilterName() {
return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
}
首先,會(huì)獲取targetBeanName 的值,這里會(huì)取當(dāng)前Filter的名稱,也即我們在web.xml中配置的 <filter-name>屬性值:springSessionRepositoryFilter,然后調(diào)用 initDelegate方法為 delegate賦值,initDelegate方法如下:
/**
* 初始化代理Filter
*/
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
//根據(jù)getTargetBeanName() 即 springSessionRepositoryFilter去WebApplicationContext查找bean
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
//調(diào)用代理Filter的init方法
delegate.init(getFilterConfig());
}
return delegate;
}
這里 根據(jù) springSessionRepositoryFilter去WebApplicationContext查找Bean,找到的Filter究竟是誰呢?
還記得我們在 applicationContext.xml中配置的 第一個(gè)bean嗎?
<!--spring session-->
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800"></property>
</bean>
RedisHttpSessionConfiguration的繼承關(guān)系如下:

Spring Session為了減輕我們配置Bean 的負(fù)擔(dān),在 RedisHttpSessionConfiguration以及它的父類 SpringHttpSessionConfiguration中 自動(dòng)生成了許多Bean,我們看看 SpringHttpSessionConfiguration的源碼:
@Configuration
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();
private boolean usesSpringSessionRememberMeServices;
private ServletContext servletContext;
private CookieSerializer cookieSerializer;
private HttpSessionStrategy httpSessionStrategy = this.defaultHttpSessionStrategy;
private List<HttpSessionListener> httpSessionListeners = new ArrayList<HttpSessionListener>();
/**
* 我們在web.xml配置的Filter名稱
*/
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
SessionRepository<S> 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;
}
}
看到這里明白了吧,根據(jù) springSessionRepositoryFilter從 WebApplicationContext取到的是 org.springframework.session.web.http.SessionRepositoryFilter對象。
接下來,我們來看看 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) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
/**
* 調(diào)用代理Filter
*/
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
DelegatingFilterProxy doFilter方法將每次請求都交給 delegate處理,即交給 org.springframework.session.web.http.SessionRepositoryFilter 進(jìn)行處理。
SessionRepositoryFilter
Spring Session對HTTP的支持所依靠的是一個(gè)簡單老式的Servlet Filter,借助servlet規(guī)范中標(biāo)準(zhǔn)的特性來實(shí)現(xiàn)Spring Session的功能。SessionRepositoryFilter就是 Servlet Filter的一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn),代碼如下:
public class SessionRepositoryFilter<S extends ExpiringSession>
extends OncePerRequestFilter {
private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class
.getName().concat(".SESSION_LOGGER");
private static final Log SESSION_LOGGER = LogFactory.getLog(SESSION_LOGGER_NAME);
private final SessionRepository<S> sessionRepository;
private ServletContext servletContext;
private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
/**
* 構(gòu)造方法
*/
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
if (sessionRepository == null) {
throw new IllegalArgumentException("sessionRepository cannot be null");
}
this.sessionRepository = sessionRepository;
}
/**
* doFilter方法調(diào)用
*/
@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);
HttpServletRequest strategyRequest = this.httpSessionStrategy
.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = this.httpSessionStrategy
.wrapResponse(wrappedRequest, wrappedResponse);
try {
filterChain.doFilter(strategyRequest, strategyResponse);
}
finally {
wrappedRequest.commitSession();
}
}
}
SessionRepositoryFilter 繼承自O(shè)ncePerRequestFilter,也是一個(gè)標(biāo)準(zhǔn)的Servlet Filter。真正的核心在于它對請求的HttpServletRequest ,HttpServletResponse 對進(jìn)行包裝了之后,然后調(diào)用 filterChain.doFilter(strategyRequest, strategyResponse); 往后傳遞,后面調(diào)用者通過 HttpServletRequest.getSession();獲得session的話,得到的將會(huì)是Spring Session 提供的 HttpServletSession實(shí)例。
其中,SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper是 SessionRepositoryFilter中的 內(nèi)部類。
我們可以在Controller 中進(jìn)行 debug 看看我們拿到的 HttpServletRequest 和 HttpSession 到底是什么?
@RestController
public class EchoController {
@RequestMapping(value = "/query", method = RequestMethod.GET)
public User query(String name, HttpServletRequest request, HttpSession session){
System.out.println(session);
User user = new User();
user.setId(15L);
user.setName(name);
user.setPassword("root");
user.setAge(28);
session.setAttribute("user", user);
return user;
}
}
在IDEA中 單步調(diào)試,結(jié)果如下:

接下來,我們分析一下 SessionRepositoryRequestWrapper 中關(guān)于 getSession()實(shí)現(xiàn):
/**
* HttpServletRequest getSession()實(shí)現(xiàn)
*/
@Override
public HttpSessionWrapper getSession() {
return getSession(true);
}
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
//從當(dāng)前請求獲取sessionId
String requestedSessionId = getRequestedSessionId();
if (requestedSessionId != null
&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {
S session = getSession(requestedSessionId);
if (session != null) {
this.requestedSessionIdValid = true;
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
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
}
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)"));
}
//為當(dāng)前請求創(chuàng)建session
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(System.currentTimeMillis());
//對Spring session 進(jìn)行包裝(包裝成HttpSession)
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
/**
* 根據(jù)sessionId獲取session
*/
private S getSession(String sessionId) {
S session = SessionRepositoryFilter.this.sessionRepository
.getSession(sessionId);
if (session == null) {
return null;
}
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
/**
* 從當(dāng)前請求獲取sessionId
*/
@Override
public String getRequestedSessionId() {
return SessionRepositoryFilter.this.httpSessionStrategy
.getRequestedSessionId(this);
}
private void setCurrentSession(HttpSessionWrapper currentSession) {
if (currentSession == null) {
removeAttribute(CURRENT_SESSION_ATTR);
}
else {
setAttribute(CURRENT_SESSION_ATTR, currentSession);
}
}
/**
* 獲取當(dāng)前請求session
*/
@SuppressWarnings("unchecked")
private HttpSessionWrapper getCurrentSession() {
return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
}
注釋寫的很清楚,就不啰嗦了。