背景:
在传统的单机web应用之中,如使用tomcat时,session是由tomcat容器管理。容器根据浏览器cookie中的sessionID来判断session是否存在以及获取session。此时session是在单机的tomcat容器中,而随着单机web应用朝着分布式集群发展,此时用户的请求从浏览器可能会负载均衡分发到不同的应用中,传统的session管理方案已经不能满足需求了,此时需要一个分布式/集群的session解决方案,所以Spring session应运而生了。
工作原理:
spring session提供了一个SessionRepositoryFilter,用于装饰request与response,重写getSession等方法,从而使应用得到的session不再是tomcat容器中的session。Spring session将session从web容器中剥离,持久到redis mongo或者mysql中。
接下来是关键类的介绍:
SessionRepositoryFilter
sessionRepositoryFilter基类的doFilter会调用下面这个方法,这里包装了原来的request与response
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
//重新包装request与response
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
}
}
SessionRepositoryResponseWrapper的getSession方法
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
//取本地缓存,如果没有则去sessionRepository中取
S requestedSession = getRequestedSession();
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, 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)"));
}
//创建session,并放入sessionRepository
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
SessionRepository
接口中主要有createSession、save、findById以及deleteById,SpringRepositoryFilter中内聚了这样的一个session仓库,这里如果我们使用springboot的话,我们创建一个SessionRepository这种类型的bean放入容器就可以了,常用的有MapSessionRepository以及RedisOperationSessionRepository,当然我们也可以自定义SessionRepository.
RedisHttpSessionConfiguration这个类是redis session的自动配置类,有兴趣的可以看看。
后面还剩一些session监听失效的内容,之后补上。