Spring Session源码解读
Spring在Servlet3.x的基础上,实现了多服务的Session共享问题,本文在源码的基础上解读
ServletContainerInitializer
ServletContainerInitializer是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架。
在Spring中,我们使用它的web功能时,需要在web.xml中依据Spring的规范新建一堆配置。这样就相当于将框架和容器紧耦合了。而在3.x后注册的功能内聚到Spring里,Spring-web就变成一个纯粹的即插即用的组件,不用依据应用环境定义一套新的配置。 Web容器启动阶段依据Java SPI获取到所有ServletContainerInitializer的实现类,然后执行ononStartup方法:
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
- 实现类需要通过Java SPI声明自己是ServletContainerInitializer的Provider。
- 可以通过@HandlesTypes注解定义希望处理类型,容器会将当前应用中所有这一类型(继承或者实现)的类放在集合参数1中传递进来。如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数1为空。
在Spring Session中应用
spring-web通过Java SPI声明了SpringServletContainerInitializer。参考spring-web.jar下的meta-inf/services。核心代码如下:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
}
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
spring-session只处理WebApplicationInitializer类型,全部实例化后再依次调用WebApplicationInitializer的onStartup接口。
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
该接口定义了一个onStartup的方法,所以实现类都需要自己实现。spring-session提供了一个抽象的公用基类–org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer,由它实现了接口的onStartup方法
public void onStartup(ServletContext servletContext)
throws ServletException {
beforeSessionRepositoryFilter(servletContext);
if(configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
insertSessionRepositoryFilter(servletContext);
afterSessionRepositoryFilter(servletContext);
}
这个方法的主要过程:
- 添加ContextLoaderListener,使用过spring-mvc的肯定对这个Listener不陌生,这个Listener是整个spring-web的核心,由它来自动装配spring的核心容器ApplicationContext。第一步中的ApplicationContext作为构造参数传进了该listener。web容器启动时会通知该listener,该listener就会启动ApplicationContext的生命周期。并将bean加载完毕。因为这是spring-web边界内的,所以就不再展开,只要知道大概做了什么事情就行了。
- 通过insertSessionRepositoryFilter注册了一个Filter,从名字知道这个Filter叫SessionRepositoryFilter,顾名思义,它肯定是通过一定方式做session持久化的。
private void insertSessionRepositoryFilter(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);
String contextAttribute = getWebApplicationContextAttribute();
if(contextAttribute != null) {
springSessionRepositoryFilter.setContextAttribute(contextAttribute);
}
registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
}
这个方法主要是动态装载了一个DelegatingFilterProxy,构造函数的入参是个静态变量,对应的值是“springSessionRepositoryFilter”。从“DelegatingFilterProxy”这个名字就大概能猜到这个filter最终会把请求delegate给具体的filter,看下入参大概就可以猜到应该是委派给“springSessionRepositoryFilter”这个filter,并且这个filter应该是由spring容器管理的,在filter生命周期的某个阶段会通过spring的依赖注入进来,并执行相关的拦截请求。 这里有一点会比较容易疑惑,spring为什么要通过一个代理类来做委派,直接注册SpringSessionRepositoryFilter不是更好。 这个类继承自GenericFilterBean,GenericFilterBean是spring抽象出来的一个filter公用接口。先看下GenericFilterBean的init方法:
public final void init(FilterConfig filterConfig) throws ServletException {
// 伪代码,利用filterConfig里的参数为filter做属性自动注入
// Let subclasses do whatever initialization they like.
initFilterBean();
}
这里只抽出了其中的核心逻辑,最后调用子类的initFilterBean DelegatingFilterProxy覆盖了父类的initFilterBean方法
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
initFilterBean方法通过spring容器来取得具体的filter bean,而WebApplicationContext对象就是上文提到的AnnotationConfigWebApplicationContext对象。ContextLoaderListener在spring容器refresh完后会把对应的容器保存进ServletContext里,默认的key值为WebApplicationContext.class.getName() + “.ROOT”。这里获取spring context的findWebApplicationContext方法就是从ServletContext里通过对应的key取得的。 至此DelegatingFilterProxy初始化完成,里面做实际处理的filter最终通过spring的依赖注入初始化并且注入进来。
启用Redis Session
Spring通过EnableRedisHttpSession注解来启用Redis Session
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession {
int maxInactiveIntervalInSeconds() default 1800;
}
该注解有两个元注解,一个是Configuration, 一个是Import, 上一篇提过Configuration,它就相当于是beans配置,而Import则相当于是beans里嵌套了另一个beans配置项。另外再介绍一下Bean这个注解,这个注解相当于是beans配置里的bean标签,它是注解在方法上的,被注解方法的返回值就是一个spring bean,而相应的方法名就是作为bean name,如果显示的设置了name属性,那就以name属性值作为bean name。 所以可以理解为EnableRedisHttpSession就是一个编程式的配置定义,而RedisHttpSessionConfiguration显然也应该是一个编程式配置定义。
容器对springSessionRepositoryFilter的依赖管理
在RedisHttpSessionConfiguration里很惊喜的发现了第一篇里被注入的“springSessionRepositoryFilter”。
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository, ServletContext servletContext) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(sessionRepository);
sessionRepositoryFilter.setServletContext(servletContext);
if(httpSessionStrategy != null) {
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
}
return sessionRepositoryFilter;
}
等价于
<bean name="springSessionRepositoryFilter" class="org.springframework.session.web.http.SessionRepositoryFilter" />
两个参数SessionRepository和ServletContext都是由Spring容器管理的依赖,SessionRepository和SpringSessionRepositoryFilter定义在同一个Configuration里。ServletContext的注入可能会比较令人疑惑,在这个Configuration没有定义它无法定义它,因为它是由Servlet容器生成的。
ContextLoaderListener
在介绍ServletContext的依赖之前,再聊下前文提过的ContextLoaderListener。它由initializer动态注册,其构造函数参数为一个WebApplicationContext,继承于ContextLoader(实际启动WebApplicationContext初始化工作的对象),构造函数参数是一个WebApplicationContext。 Servlet容器启动时会通知ContextLoaderListener:
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
ContextLoaderListener会调用父类ContextLoader的initWebApplicationContext方法来启动ApplicationContext的初始化
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
configureAndRefreshWebApplicationContext(cwac, servletContext);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
return this.context;
}
上面只抽取了ContextLoader的关键逻辑
- ApplicationContext被默认放入了ServletContext中,key是个默认值。上一篇提过在获取对应name的filter时会从ServletContext里先得ApplicationContext,再获取对应依赖。
- configureAndRefreshWebApplicationContext这个方法最终启动了ApplictionContext的最核心refresh,这个方法被放在公共抽象类AbstractApplicationContext里,几乎所有的ApplicationContext都会执行。这个阶段结束后,ApplicationContext也就启动完毕,整个容器的依赖也就完成。因为篇幅太长ApplicationContext启动的细节就不再展开。
ServletContext的依赖
在容器refresh阶段会通过postProcessBeanFactory这个策略方法对容器对应的BeanFactory做后处理,一般是设置一些beanPostProcessor之类,对于WebApplicationContext则会处理ServletContext的依赖。
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
//在这里将ServletContext作为一个bean管理起来WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
至此,springSessionRepositoryFilter的依赖也注入完毕。
ServletRequestWrapper
Servlet规范从2.3起引入了ServletRequestWrapper包装类,它把调用交给被包装的ServletRequest来执行。这样就可以对ServletRequest进行扩展。例如Tomcat就是将自己的Request类作为包装类的实体。
public class ServletRequestWrapper implements ServletRequest {
private ServletRequest request;
public ServletRequestWrapper(ServletRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request cannot be null");
}
this.request = request;
}
public ServletRequest getRequest() {
return this.request;
}
public Object getAttribute(String name) {
return this.request.getAttribute(name);
}
}
为了更好的支持HttpServletRequest,Servlet2.3还支持了提供了HttpServletRequestWrapper,其实现了接口HttpServletRequest,并且继承了ServletRequestWrapper。
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest {
public HttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
private HttpServletRequest _getHttpServletRequest() {
return (HttpServletRequest) super.getRequest();
}
......
}
SessionRepositoryRequestWrapper继承了HttpServletWrapper,并覆盖了getSession方法,通过spring自己的策略生成session。
- Spring定义了新的response wrapper–OnCommittedResponseWrapper,其关联了自实现的字符和字节输出流,并定义了一个模板方法onResponseCommitted,由继承子类来实现。
- OnCommittedResponseWrapper关联了自己实现的一个字节流和字符流。他们和普通的字节字符流一样也是个包装类。
通过SessionRepositoryFilter对Servlet侵入
spring-session通过Filter将自定义的Request wrapper和Response Wrapper侵入到Servlet容器中。 在Tomcat中Servlet的service方法会由ApplicationFilterChain调用,而FilterChain的参数通过Filter的filterChain.doFilter传入。Spring-session通过自定义的全局拦截器SessionRepositoryFilter将request和response侵入进容器。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response, servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,response);
HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);
try {
filterChain.doFilter(strategyRequest, strategyResponse);
} finally {
wrappedRequest.commitSession();
}
}
SessionRepositoryFilter继承了OncePerRequestFilter,父类的doFilter最终会将具体的处理逻辑交给子类处理。
abstract class OncePerRequestFilter implements Filter {
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//alreadyFilteredAttributeName是个静态变量,由类名+.filtered构成
//这样就能保证同一个类只被调用一次。
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (hasAlreadyFilteredAttribute) {
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
//通过模板方法,将处理交给子类
doFilterInternal(httpRequest, httpResponse, filterChain);
}
}
}
}
OncePerRequestFilter是用来保证一次完整的拦截链中,同一个类只会被调用一次。 因为spring无法保证同一个Filter类只有一个实例。有可能一个Filter既有可能在web.xml里配置由容器初始化了,还有可能被作为spring的依赖引入进了DelegatingFilterProxy。这样在一次filter chain中就会存在同一个Filter的多个实例。
Session获取
spring-session实现了HttpServletRequest的子类–SessionRepositoryRequestWrapper,由它覆盖getSession方法,将由web容器处理的逻辑接管过来。
public HttpSession getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if(currentSession != null) {
return currentSession;
}
String requestedSessionId = getRequestedSessionId();
if(requestedSessionId != null) {
S session = sessionRepository.getSession(requestedSessionId);
if(session != null) {
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(session, getServletContext());
//从数据仓库提取出来的session状态不为new
currentSession.setNew(false);
setCurrentSession(currentSession);
return currentSession;
}
}
if(!create) {
return null;
}
S session = sessionRepository.createSession();
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
session id存在的情况下,如果通过session id能找到持久化的session就直接返回,否则根据create是否为true决定是否新建一个Session。Session在后续的使用中会陆续更改,添加、删除或者修改属性值。从数据仓库中提取出来的session会默认修改lastAccessTime属性以避免session失效。 数据仓库中的持久化session也存在失效时间,消费端可以通过RedisHttpSessionConfiguration.setMaxInactiveIntervalInSeconds(long timemills)来设置,默认是1800秒。Redis会定时清除过期数据。
spring-session的Session实现
Spring为了将Session与具体的协议分开,单独提炼出Session实体,再通过HttpSessionWrapper将session包装起来,从而扩展HttpSession.如果以后还需要支持另外一种应用协议,就只要增加一种应用类型的wrapper就行了。
private final class HttpSessionWrapper implements HttpSession {
public HttpSessionWrapper(S session, ServletContext servletContext) {
this.session = session;
this.servletContext = servletContext;
}
//省略了大部分方法,都是委托给被包装的Session处理的
//对session坐invalidate时去数据仓库删掉对应的数据
public void invalidate() {
checkState();
this.invalidated = true;
requestedSessionInvalidated = true;
setCurrentSession(null);
sessionRepository.delete(getId());
}
public void setNew(boolean isNew) {
this.old = !isNew;
}
public boolean isNew() {
checkState();
return !old;
}
}
Session实体主要定义通用的getAttribute和setAttribute等方法。此外扩展了一个ExpiringSession,这是spring的默认session,它主要用来判断session是否失效。
public interface ExpiringSession extends Session {
//session的创建时间
long getCreationTime();
//session的上次访问时间
long getLastAccessedTime();
//设置最大访问间隔,超过这个间隔session会被invalidate
void setMaxInactiveIntervalInSeconds(int interval);
int getMaxInactiveIntervalInSeconds();
//session是否失效
boolean isExpired();
}
session提交
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if(wrappedSession == null) {
if(isInvalidateClientSession()) {
httpSessionStrategy.onInvalidateSession(this, response);
}
} else {
S session = wrappedSession.session;
sessionRepository.save(session);
if(!isRequestedSessionIdValid() || !session.getId().equals(getRequestedSessionId())) {
httpSessionStrategy.onNewSession(session, this, response);
}
}
}
- getSession时会通过setCurrentSession把新建或者提取出来的session放到request的HttpServletRequestWrapper.class.getName()属性上;相应的在session invalidate时也会将session从request里移除掉,此时通过getCurrentSession将取不到数据。
- 在做session持久化时,会首先判断session是否被invalidate,如果是则会删除
session通用策略
Session在浏览器通常是通过cookie保存的,cookie里保存了jessionid,代表用户的session id。一个访问路径只有一个session cookie(事实上在客户端就只有一个cookie,jsessionid是作为cookie值的一部分,这里把cookie抽象成类似服务器端的实现),也就是一个访问路径在一个浏览器上只有一个session,这是绝大多数容器对session的实现。而spring却可以支持单浏览器多用户session。下面就看看spring是怎样去支持多用户session的。 spring session通过增加session alias概念来实现多用户session,每一个用户都映射成一个session alias。当有多个session时,spring会生成“alias1 sessionid1 alias2 sessid2…….”这样的cookie值结构。 spring session提交时如果有新session生成,会触发onNewSession动作生成新的session cookie
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
Set<String> sessionIdsWritten = getSessionIdsWritten(request);
if(sessionIdsWritten.contains(session.getId())) {
return;
}
sessionIdsWritten.add(session.getId());
Map<String,String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
Cookie sessionCookie = createSessionCookie(request, sessionIds);
response.addCookie(sessionCookie);
}
- 确保已经存在cookie里的session不会再被处理。
- 生成一个包含所有alias的session id的map,并通过这个map构造新的session cookie值。
createSessionCookie会根据一个alias-sessionid的map去构造session cookie。
private Cookie createSessionCookie(HttpServletRequest request, Map<String, String> sessionIds) {
//cookieName是"SESSION",spring的session cookie都是
//以"SESSION"命名的
Cookie sessionCookie = new Cookie(cookieName,"");
//省略部分非关键逻辑
if(sessionIds.isEmpty()) {
sessionCookie.setMaxAge(0);
return sessionCookie;
}
if(sessionIds.size() == 1) {
String cookieValue = sessionIds.values().iterator().next();
sessionCookie.setValue(cookieValue);
return sessionCookie;
}
StringBuffer buffer = new StringBuffer();
for(Map.Entry<String,String> entry : sessionIds.entrySet()) {
String alias = entry.getKey();
String id = entry.getValue();
buffer.append(alias);
buffer.append(" ");
buffer.append(id);
buffer.append(" ");
}
buffer.deleteCharAt(buffer.length()-1);
sessionCookie.setValue(buffer.toString());
return sessionCookie;
}
- 当session被invalidate,可能会存在seesionids为空的情况,这种情况下将session cookie的最大失效时间设成立即。
- 如果只有一个session id,则和普通session cookie一样处理,cookie值就是session id。
- 如果存在多个session id,则生成前文提到的session cookie值结构。
session cookie的获取
getSessionIds方法会取出request里的session cookie值,并且对每种可能的值结构进行相应的格式化生成一个key-value的map。
public Map<String,String> getSessionIds(HttpServletRequest request) {
Cookie session = getCookie(request, cookieName);
String sessionCookieValue = session == null ? "" : session.getValue();
Map<String,String> result = new LinkedHashMap<String,String>();
StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");
//单用户cookie的情况
if(tokens.countTokens() == 1) {
result.put(DEFAULT_ALIAS, tokens.nextToken());
return result;
}
while(tokens.hasMoreTokens()) {
String alias = tokens.nextToken();
if(!tokens.hasMoreTokens()) {
break;
}
String id = tokens.nextToken();
result.put(alias, id);
}
return result;
}
- 对单用户session cookie的处理,只取出值,默认为是默认别名(默认为0)用户的session。
- 对多用户,则依据值结构的格式生成alias-sessionid的map。
- 以上两种格式化都是对创建session的逆操作。
getCurrentSessionAlias用来获取当前操作用户。可以通过在request里附加alias信息,从而让spring可以判断是哪个用户在操作。别名是通过”alias name=alias”这样的格式传入的,alias name默认是_s,可以通过setSessionAliasParamName(String)方法修改。我们可以在url上或者表单里添加”_s=your user alias”这样的形式来指明操作用户的别名。如果不指明用户别名,则会认为是默认用户,可以通过setSessionAliasParamName(null)取消别名功能。
public String getCurrentSessionAlias(HttpServletRequest request) {
if(sessionParam == null) {
return DEFAULT_ALIAS;
}
String u = request.getParameter(sessionParam);
if(u == null) {
return DEFAULT_ALIAS;
}
if(!ALIAS_PATTERN.matcher(u).matches()) {
return DEFAULT_ALIAS;
}
return u;
}
触发session提交
spring会通过两个方面确保session提交:
- response提交,主要包括response的sendRedirect和sendError以及其关联的字节字符流的flush和close方法。
abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
public OnCommittedResponseWrapper(HttpServletResponse response) {
super(response);
}
/**
* Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse} being committed
*/
protected abstract void onResponseCommitted();
@Override
public final void sendError(int sc) throws IOException {
doOnResponseCommitted();
super.sendError(sc);
}
//sendRedirect处理类似sendError
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new SaveContextServletOutputStream(super.getOutputStream());
}
@Override
public PrintWriter getWriter() throws IOException {
return new SaveContextPrintWriter(super.getWriter());
}
private void doOnResponseCommitted() {
if(!disableOnCommitted) {
onResponseCommitted();
disableOnResponseCommitted();
} else if(logger.isDebugEnabled()){
logger.debug("Skip invoking on");
}
}
private class SaveContextPrintWriter extends PrintWriter {
private final PrintWriter delegate;
public SaveContextPrintWriter(PrintWriter delegate) {
super(delegate);
this.delegate = delegate;
}
public void flush() {
doOnResponseCommitted();
delegate.flush();
}
//close方法与flush方法类似
}
//SaveContextServletOutputStream处理同字符流
}
onResponseCommitted的实现由子类SessionRepositoryResponseWrapper提供
private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper {
private final SessionRepositoryRequestWrapper request;
/**
* @param response the response to be wrapped
*/
public SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) {
super(response);
if(request == null) {
throw new IllegalArgumentException("request cannot be null");
}
this.request = request;
}
@Override
protected void onResponseCommitted() {
request.commitSession();
}
}
response提交后触发了session提交。
- SessionRespositoryFilter
仅仅通过response提交时触发session提交并不能完全保证session的提交,有些情况下不会触发response提交,比如对相应资源的访问没有servlet处理,这种情况就需要通过全局filter做保证。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
//省略
//filterChain会在所有filter都执行完毕后调用对应的servlet
filterChain.doFilter(strategyRequest, strategyResponse);
} finally {
//所有的处理都完成后提交session
wrappedRequest.commitSession();
}
}