白话 spring session 源码

项目中一直使用spring session但是以前都是只有在使用没有深入了解其原理,这次碰巧项目不是很忙仔细研究了一下他的原理

spring session 的使用

使用很简单网上一大堆教程这里面就简单说一下
引入jar包

 <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
复制代码

注解

@EnableRedisHttpSession(redisNamespace=“xxxx”)
复制代码

不用这个好像也可以,他可以修改你session存放在redis中的前缀

源码解析

我们主要关注一下几个类

EnableRedisHttpSession  // 注解
RedisHttpSessionConfiguration  // 加载redis 的配置类
RedisIndexedSessionRepository // spring session 中 redis的操作类
SpringHttpSessionConfiguration // 初始化 springsession过滤器的配置类
SessionRepositoryFilter  // spring session 的过滤器,在这里获取session 创建session 保存session
CookieHttpSessionIdResolver  // cookie 转化成sessionId的转化器
SessionRepositoryRequestWrapper // 重写了request的getsession方法,重写之后会从redis中获取
复制代码

通过讲解上述几个类大家基本上也就会理解整个springsession的中session如何保存进redis,又如何从redis中取出来的

EnableRedisHttpSession

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RedisHttpSessionConfiguration.class) //首先要引入这个redissession的配置类
@Configuration(proxyBeanMethods = false)
public @interface EnableRedisHttpSession {

	/**
	 * The session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
	 * This should be a non-negative integer.
	 * @return the seconds a session can be inactive before expiring
	 */
	 //session 在redis中的失效时间
	int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;

	/**
	 * Defines a unique namespace for keys. The value is used to isolate sessions by
	 * changing the prefix from default {@code spring:session:} to
	 * {@code <redisNamespace>:}.
	 * <p>
	 * For example, if you had an application named "Application A" that needed to keep
	 * the sessions isolated from "Application B" you could set two different values for
	 * the applications and they could function within the same Redis instance.
	 * @return the unique namespace for keys
	 */
	 //在redis中的前缀名称
	String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE;

	/**
	 * Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
	 * updates the backing Redis when {@link SessionRepository#save(Session)} is invoked.
	 * In a web environment this happens just before the HTTP response is committed.
	 * <p>
	 * Setting the value to {@code IMMEDIATE} will ensure that the any updates to the
	 * Session are immediately written to the Redis instance.
	 * @return the {@link RedisFlushMode} to use
	 * @since 1.1
	 * @deprecated since 2.2.0 in favor of {@link #flushMode()}
	 */
	@Deprecated
	RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
复制代码

看了上述的代码,应该对这个有了一个大致的概念,在项目中我们是经常要设置session的失效时间,不同的服务redis中的保存前缀也是不同的,之后我们也应该知道接下来我们要分析哪一个类了,没错就是他要引入的RedisHttpSessionConfiguration这个类

RedisHttpSessionConfiguration

在这个类里面你可以理解它主要就是配置了一些redis相关的东西

    // sessionRepository主要就是 session 保存在redis中的保存期
    @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;
	}


// redis 的监听器,一般监听的是 session在redis中删除或者是到达了失效时间
	@Bean
	public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
			RedisIndexedSessionRepository sessionRepository) {
		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
		container.setConnectionFactory(this.redisConnectionFactory);
		if (this.redisTaskExecutor != null) {
			container.setTaskExecutor(this.redisTaskExecutor);
		}
		if (this.redisSubscriptionExecutor != null) {
			container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
		}
		container.addMessageListener(sessionRepository,
				Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
						new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
		container.addMessageListener(sessionRepository,
				Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
		return container;
	}
复制代码

RedisIndexedSessionRepository

这个类中主要就是redis的相关操作,主要我们可以关注一下两个方法:
1 保存session
2 通过sessionId获取session

	@Override
	public void save(RedisSession session) {
		session.save();
		if (session.isNew) {
			String sessionCreatedKey = getSessionCreatedChannel(session.getId());
			this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
			session.isNew = false;
		}
	}

	public void cleanupExpiredSessions() {
		this.expirationPolicy.cleanExpiredSessions();
	}

	@Override
	public RedisSession findById(String id) {
		return getSession(id, false);
	}
复制代码

SpringHttpSessionConfiguration

@Bean
	public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
			SessionRepository<S> sessionRepository) {
		SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
		sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); //sessionId转化器 
		return sessionRepositoryFilter;
	}
复制代码

在这个配置类中主要注册了sessionRepositoryFilter 过滤器,可以看见sessionRepositoryFilter 设置了一个HttpSessionIdResolver,看名字就可以知道这是sessionId的转化器,就是生成sessionId的的类,可以看一下代码中默认的转化器

@Configuration(proxyBeanMethods = false)
public class SpringHttpSessionConfiguration implements ApplicationContextAware {

	private final Log logger = LogFactory.getLog(getClass());

	private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();

	private boolean usesSpringSessionRememberMeServices;

	private ServletContext servletContext;

	private CookieSerializer cookieSerializer;

	private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;

复制代码

通过上述代码就可以清晰的看见默认的sessionId转化器是CookieHttpSessionIdResolver,接下来看一下sessionId是如何生成

CookieHttpSessionIdResolver

//通过cookie获取sessionId
@Override
	public List<String> resolveSessionIds(HttpServletRequest request) {
		return this.cookieSerializer.readCookieValues(request);
	}

// 将sessionId 写回cookie中
	@Override
	public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {
		if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
			return;
		}
		request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
		this.cookieSerializer.writeCookieValue(new CookieValue(request, response, sessionId));
	}
	
	//具体的方法获取sessionId
	@Override
	public List<String> readCookieValues(HttpServletRequest request) {
		Cookie[] cookies = request.getCookies();
		List<String> matchingCookieValues = new ArrayList<>();
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				if (this.cookieName.equals(cookie.getName())) {
					String sessionId = (this.useBase64Encoding ? base64Decode(cookie.getValue()) : cookie.getValue());
					if (sessionId == null) {
						continue;
					}
					if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
						sessionId = sessionId.substring(0, sessionId.length() - this.jvmRoute.length());
					}
					matchingCookieValues.add(sessionId);
				}
			}
		}
		return matchingCookieValues;
	}
复制代码

通过上述代码就可以知道resolveSessionIds获取sessionId, resolveSessionIds真正的执行方法是 readCookieValues,可以看见sessionId的获取的方法实际就是将cookie中的value进行base64转码。

SessionRepositoryFilter

接下来就是重中之重了spring session过滤器,一切的一切都是从这里开始的

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> 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);

	/**
	 * The session repository request attribute name.
	 */
	public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class.getName();

	/**
	 * Invalid session id (not backed by the session repository) request attribute name.
	 */
	public static final String INVALID_SESSION_ID_ATTR = SESSION_REPOSITORY_ATTR + ".invalidSessionId";

	private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR + ".CURRENT_SESSION";

	/**
	 * The default filter order.
	 */
	public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;

	private final SessionRepository<S> sessionRepository;

	private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();

	/**
	 * Creates a new instance.
	 * @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
	 */
	public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
		if (sessionRepository == null) {
			throw new IllegalArgumentException("sessionRepository cannot be null");
		}
		this.sessionRepository = sessionRepository;
	}

	/**
	 * Sets the {@link HttpSessionIdResolver} to be used. The default is a
	 * {@link CookieHttpSessionIdResolver}.
	 * @param httpSessionIdResolver the {@link HttpSessionIdResolver} to use. Cannot be
	 * null.
	 */
	public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
		if (httpSessionIdResolver == null) {
			throw new IllegalArgumentException("httpSessionIdResolver cannot be null");
		}
		this.httpSessionIdResolver = httpSessionIdResolver;
	}


//过滤器真正执行逻辑的方法
	@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 {
		//在这个方法中会将session保存在redis中
			wrappedRequest.commitSession();
		}
	}
复制代码

上面就是filter具体的执行逻辑,这里我们首先看一下SessionRepositoryRequestWrapper这个类,该类主要关注的就是getSession方法,他重写了request的getSession方法,变成了从redis中获取

SessionRepositoryRequestWrapper

写出几个关键的方法

private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {

		private final HttpServletResponse response;

		private S requestedSession;

		private boolean requestedSessionCached;

		private String requestedSessionId;

		private Boolean requestedSessionIdValid;

		private boolean requestedSessionInvalidated;

		private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
			super(request);
			this.response = response;
		}

		/**
		 * Uses the {@link HttpSessionIdResolver} to write the session id to the response
		 * and persist the Session.
		 */
		 ---------------------------------------------
		 //保存session进redis中,设置超时时间
		private void commitSession() {
			HttpSessionWrapper wrappedSession = getCurrentSession();
			if (wrappedSession == null) {
				if (isInvalidateClientSession()) {
					SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
				}
			}
			else {
				S session = wrappedSession.getSession();
				clearRequestedSessionCache();
				//保存进redis
				SessionRepositoryFilter.this.sessionRepository.save(session);
				String sessionId = session.getId();
				if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) {
				//将session 写入cookie
					SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
				}
			}
		}
		
	
	----------------------------------------------------------	
		//从request中获取当前的session,这样再一次请求中不会每次都去redis中查找session
			private HttpSessionWrapper getCurrentSession() {
			return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
		}

		private void setCurrentSession(HttpSessionWrapper currentSession) {
			if (currentSession == null) {
				removeAttribute(CURRENT_SESSION_ATTR);
			}
			else {
				setAttribute(CURRENT_SESSION_ATTR, currentSession);
			}
		}
	------------------------------------------------------------	
	//获取session 贼鸡儿重要!!!!!
	
	@Override
		public HttpSessionWrapper getSession(boolean create) {
		//首先 request中获取 当前session,没有的话从redis
		中获取
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			//这个方法就是讲cookie转化成sessionId之后从redis中获取session,具体代码在下面
			S requestedSession = getRequestedSession();
			//redis中如果有域名直接返回  一般第二次的请求都是走到这里就结束了,从redis中获取session
			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");
			}
			// 获取session的时候会带上是否创建create标识
			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)"));
			}
			//创建redis session
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			//将新创建的session放入request的当前session中
			setCurrentSession(currentSession);
			return currentSession;
		}
		
		
		//上面的从redis中获取session 的方法 首先通过 sessionId转换器将cookie转换成sessionId 之后再redis中找找
		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;
		}
		
复制代码

spring session 保存时序图

这里其实获取session也基本上和保存的的流程差不多所以只贴了保存的时序图
image

相信看了上述关键类代码的讲解,再看了时序图大家基本上对springsession的整个运行流程都有了大致的了解。

哈哈哈以上就是我分享的内容,如果有什么讲的不对的地方,及时给我留言我立刻改正,要是误导了其他的正在学的小朋友,那老夫就罪孽深重了哈哈哈哈

image

哈哈

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享