Spring Session 核心原理分析

  1. 要使用 Spring Session ,如用 Redis 作为 Session 的存储,需要用到注解 @EnableRedisHttpSession

  2. 从@EnableRedisHttpSession的源码开始

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(RedisHttpSessionConfiguration.class)
    @Configuration(proxyBeanMethods = false)
    public @interface EnableRedisHttpSession {
      ...
    }
    

    从代码中可以看出,导入了配置类 RedisHttpSessionConfiguration

  3. 分析 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 类的源码

  4. 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 类的源码

  5. 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 请求。

  6. 通过前面几步的分析,我们至少知道,容器中 放入了 RedisIndexedSessionRepository 组件 和 SessionRepositoryFilter 组件 这两个组件。RedisIndexedSessionRepository 该组件用于对 Redis 中 Session数据的增删改查,而SessionRepositoryFilter 该组件用于对Http请求的过滤。

  7. 现在分析 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 .

  8. 以后调用 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 数据。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值