-
要使用 Spring Session ,如用 Redis 作为 Session 的存储,需要用到注解 @EnableRedisHttpSession
-
从@EnableRedisHttpSession的源码开始
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(RedisHttpSessionConfiguration.class) @Configuration(proxyBeanMethods = false) public @interface EnableRedisHttpSession { ... }
从代码中可以看出,导入了配置类 RedisHttpSessionConfiguration
-
分析 RedisHttpSessionConfiguration 类的源码
@Configuration(proxyBeanMethods = false) public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware { ... @Bean public RedisIndexedSessionRepository sessionRepository() { RedisTemplate<Object, Object> redisTemplate = createRedisTemplate(); RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate); sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher); if (this.indexResolver != null) { sessionRepository.setIndexResolver(this.indexResolver); } if (this.defaultRedisSerializer != null) { sessionRepository.setDefaultSerializer(this.defaultRedisSerializer); } sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); if (StringUtils.hasText(this.redisNamespace)) { sessionRepository.setRedisKeyNamespace(this.redisNamespace); } sessionRepository.setFlushMode(this.flushMode); sessionRepository.setSaveMode(this.saveMode); int database = resolveDatabase(); sessionRepository.setDatabase(database); this.sessionRepositoryCustomizers .forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository)); return sessionRepository; } ... }
从源码中可以看出,向 Spring 的容器中放入了 RedisIndexedSessionRepository 组件。我们都清楚:Repository 组件一般用来操作数据持久层的,因此这里的 RedisIndexedSessionRepository 就是用来操作 Redis 中关于 Session数据的增删改查的。如下图:
RedisHttpSessionConfiguration 类 继承自 SpringHttpSessionConfiguration 类,现在我们看一下 SpringHttpSessionConfiguration 类的源码 -
SpringHttpSessionConfiguration 类源码
@Configuration(proxyBeanMethods = false) public class SpringHttpSessionConfiguration implements ApplicationContextAware { .... @Bean public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter( SessionRepository<S> sessionRepository) { SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); return sessionRepositoryFilter; } .... }
分析该类,我们可以发现,该类向 Spring 的容器中放入了 springSessionRepositoryFilter 这样一个 Filter 组件。现在我们看一下 SessionRepositoryFilter 类的源码
-
SessionRepositoryFilter 类源码
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { } abstract class OncePerRequestFilter implements Filter { } public interface Filter { }
我们可以发现 SessionRepositoryFilter 类 继承自 OncePerRequestFilter 类,而OncePerRequestFilter 类实现了 Filter 接口。
而该 Filter 就是Servlet的 Filter。因此 SessionRepositoryFilter 可以过滤所有的 Http 请求。 -
通过前面几步的分析,我们至少知道,容器中 放入了 RedisIndexedSessionRepository 组件 和 SessionRepositoryFilter 组件 这两个组件。RedisIndexedSessionRepository 该组件用于对 Redis 中 Session数据的增删改查,而SessionRepositoryFilter 该组件用于对Http请求的过滤。
-
现在分析 SessionRepositoryFilter 类中的 doFilterInternal 方法,该方法是整个 Spring Session 的核心方法
@Order(SessionRepositoryFilter.DEFAULT_ORDER) public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { ... @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); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response); try { filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { wrappedRequest.commitSession(); } } .... }
我们看到该方法重写了 OncePerRequestFilter 类的 doFilterInternal 方法,并且从源码中可以看出,原生的HttpServletRequest 类的对象 request 和 HttpServletResponse 类的对象 response 利用
装饰器模式
被包装成了 SessionRepositoryRequestWrapper 类的对象 wrappedRequest 和 SessionRepositoryResponseWrapper 类的对象 wrappedResponse 。接着调用filterChain.doFilter(wrappedRequest, wrappedResponse);
放行了 Http 请求。因此,我们知道,最终到达 Controller 中的 request 和 response 是经过包装后的的 request 和 response . -
以后调用 request.getSession()方法,实际上就是调用 SessionRepositoryRequestWrapper 类的 getSession方法。该方法重写了原生HttpServletRequest 的 getSession方法,重写意味着改变了getSession()的默认行为。源码如下:
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { ... private final SessionRepository<S> sessionRepository; public SessionRepositoryFilter(SessionRepository<S> sessionRepository) { if (sessionRepository == null) { throw new IllegalArgumentException("sessionRepository cannot be null"); } this.sessionRepository = sessionRepository; } @Override public HttpSessionWrapper getSession(boolean create) { HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession != null) { return currentSession; } 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.markNotNew(); 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 (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver && this.response.isCommitted()) { throw new IllegalStateException("Cannot create a session after the response has been committed"); } 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)")); } S session = SessionRepositoryFilter.this.sessionRepository.createSession(); session.setLastAccessedTime(Instant.now()); currentSession = new HttpSessionWrapper(session, getServletContext()); setCurrentSession(currentSession); return currentSession; } private S getRequestedSession() { if (!this.requestedSessionCached) { List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this); for (String sessionId : sessionIds) { if (this.requestedSessionId == null) { this.requestedSessionId = sessionId; } S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId); if (session != null) { this.requestedSession = session; this.requestedSessionId = sessionId; break; } } this.requestedSessionCached = true; } return this.requestedSession; } ... }
从源码中,我们知道,现在对 Session 的操作都是通过 sessionRepository 来操作的,而 sessionRepository 对象,恰好就是我们之前看源码发现的,往Spring 容器中放的两个组件之一的 RedisIndexedSessionRepository 类的对象。因此,现在 getSession()执行的操作,都是操作 Redis 中存储的 Session 数据。
Spring Session 核心原理分析
最新推荐文章于 2024-04-30 14:07:59 发布