【干啥啥不行,写Bug第一名】Spring Session 源码浅浅析

Spring Session 源码浅浅析

org.springframework.session.Session 是 spring-session 对 Session 的抽象,主要是为了鉴定用户,为 HTTP请求和响应提供上下文过程,该 Session 可以被 HttpSession、WebSocket Session,非WebSession等使用。
org.springframework.session.Session 定义了Session的基本行为:
getId:获取 sessionId
setAttribute:设置 session 属性
getAttribte:获取 session 属性

org.springframework.session.Session 有两种 Spring 的实现:
MapSession:基于 java.util.Map 实现
RedisSession:基于 MapSession 和 Redis 实现,提供 Session 的持久化能力



从一个平平无奇的 Spring Boot 项目开始讲起…


依赖引入

pom.xml

...
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
...
    <dependencies>
...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
...
    </dependencies>
</project>

较高版本的 Spring Boot,如:2.7.2 的 Spring Session 有改动,具体改动大小没研究过

application.yaml

spring:
  redis:
    host: localhost
    password: hh123456

Spring Boot 自动装配寻找配置类

spring.factories

...
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
...

SessionAutoConfiguration.java
这个类干了些啥

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Spring Session.
 *
 * @author Andy Wilkinson
 * @author Tommy Ludwig
 * @author Eddú Meléndez
 * @author Stephane Nicoll
 * @author Vedran Pavic
 * @since 1.4.0
 */
@Configuration
@ConditionalOnClass(Session.class)
@ConditionalOnWebApplication
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
		JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class,
		MongoReactiveDataAutoConfiguration.class, RedisAutoConfiguration.class,
		RedisReactiveAutoConfiguration.class })
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
public class SessionAutoConfiguration {

	@Configuration
	@ConditionalOnWebApplication(type = Type.SERVLET)
	@Import({ ServletSessionRepositoryValidator.class,
			SessionRepositoryFilterConfiguration.class })
	static class ServletSessionConfiguration {
	...
	}
...
}

SessionProperties.class
Spring Session 配置类

RedisAutoConfiguration.class
创建 redisTemplate 和 stringRedisTemplate

Session 配置类

RedisSessionConfiguration.java

/**
 * Redis backed session configuration.
 *
 * @author Andy Wilkinson
 * @author Tommy Ludwig
 * @author Eddú Meléndez
 * @author Stephane Nicoll
 * @author Vedran Pavic
 */
@Configuration
@ConditionalOnClass({ RedisTemplate.class, RedisOperationsSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisSessionConfiguration {

	@Configuration
	public static class SpringBootRedisHttpSessionConfiguration
			extends RedisHttpSessionConfiguration {

		@Autowired
		public void customize(SessionProperties sessionProperties,
				RedisSessionProperties redisSessionProperties) {
			Duration timeout = sessionProperties.getTimeout();
			if (timeout != null) {
				setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
			}
			setRedisNamespace(redisSessionProperties.getNamespace());
			setRedisFlushMode(redisSessionProperties.getFlushMode());
			setCleanupCron(redisSessionProperties.getCleanupCron());
		}

	}

}

在 RedisHttpSessionConfiguration 创建了 sessionRepository bean

	@Bean
	public RedisOperationsSessionRepository sessionRepository() {
		RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
		RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
				redisTemplate);
		sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
		if (this.defaultRedisSerializer != null) {
			sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
		}
		sessionRepository
				.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
		if (StringUtils.hasText(this.redisNamespace)) {
			sessionRepository.setRedisKeyNamespace(this.redisNamespace);
		}
		sessionRepository.setRedisFlushMode(this.redisFlushMode);
		int database = resolveDatabase();
		sessionRepository.setDatabase(database);
		return sessionRepository;
	}

在 SpringHttpSessionConfiguration 中,注入 sessionRepository,创建 springSessionRepositoryFilter bean
这个 bean 是 SessionRepositoryFilter extends OncePerRequestFilter implements Filter
可以看出,这是一个 Filter,一个过滤器

...
	@Bean
	public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
			SessionRepository<S> sessionRepository) {
		SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
				sessionRepository);
		sessionRepositoryFilter.setServletContext(this.servletContext);
		sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
		return sessionRepositoryFilter;
	}
...

在 SessionRepositoryFilterConfiguration
把 springSessionRepositoryFilter 注册进容器

/**
 * Configuration for customizing the registration of the {@link SessionRepositoryFilter}.
 *
 * @author Andy Wilkinson
 */
@Configuration
@ConditionalOnBean(SessionRepositoryFilter.class)
@EnableConfigurationProperties(SessionProperties.class)
class SessionRepositoryFilterConfiguration {

	@Bean
	public FilterRegistrationBean<SessionRepositoryFilter<?>> sessionRepositoryFilterRegistration(
			SessionProperties sessionProperties, SessionRepositoryFilter<?> filter) {
		// 下面这句话把 springSessionRepositoryFilter 注册进容器
		FilterRegistrationBean<SessionRepositoryFilter<?>> registration = new FilterRegistrationBean<>(
				filter);
		registration.setDispatcherTypes(getDispatcherTypes(sessionProperties));
		registration.setOrder(sessionProperties.getServlet().getFilterOrder());
		return registration;
	}
...
}

当请求进来时,Filter 开始表演

当有请求进来时,由于 SessionRepositoryFilter 的优先级很高
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
请求会先走这个过滤器

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
...
}

之后走进
org.springframework.session.web.http.OncePerRequestFilter#doFilter

之后走进
org.springframework.session.web.http.SessionRepositoryFilter#doFilterInternal

	@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, this.servletContext);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
				wrappedRequest, response);

		try {
			// 执行其他过滤器,创建 Session等
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
			// finally,保证请求的 Session 始终会被提交
			wrappedRequest.commitSession();
		}
	}

Session 创建

		@Override
		public HttpSessionWrapper getSession(boolean create) {
			// 从当前请求的 attribute 中获取 session,如果有直接返回
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			S requestedSession = getRequestedSession();
			// 第一次访问,啥 session 都找不到,不会进入这里边
			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 {
				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
			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 创建 spring session
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			// 设置 session 的最新访问时间
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			// 设置 session 到 requset 的 attribute 中,提高同一个 request 访问 session 的性能
			setCurrentSession(currentSession);
			return currentSession;
		}

创建 Session 的时候,都干了些什么

	@Override
	public RedisSession createSession() {
		// 创建一个 redisSession 实例
		RedisSession redisSession = new RedisSession();
		// 设置 maxInactiveInterval(最大过期时间)
		if (this.defaultMaxInactiveInterval != null) {
			redisSession.setMaxInactiveInterval(
					Duration.ofSeconds(this.defaultMaxInactiveInterval));
		}
		return redisSession;
	}

	/**
	 * Creates a new instance ensuring to mark all of the new attributes to be
	 * persisted in the next save operation.
	 */
	RedisSession() {
		// Session 的两种方案:Redis vs Map
		// RedisSession 是在 MapSession 的基础上做的
		// 设置本地缓存为 MapSession
		this(new MapSession());
		// 设置 Session 的基本属性
		this.delta.put(CREATION_TIME_ATTR, getCreationTime().toEpochMilli());
		this.delta.put(MAX_INACTIVE_ATTR, (int) getMaxInactiveInterval().getSeconds());
		this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime().toEpochMilli());
		// 标记 Session 的是否为新创建
		this.isNew = true;
		this.flushImmediateIfNecessary();
	}

	/**
	 * Creates a new instance with a secure randomly generated identifier.
	 */
	public MapSession() {
		this(generateId());
	}

	// sessionId 是 UUID 生的
	private static String generateId() {
		return UUID.randomUUID().toString();
	}

最后,保存 Session,即,spring session的持久化
执行 FilterChain 中使用 finally 保证请求的 Session 始终会被提交
此提交操作中,既将 Session 持久化至存储器(如:Redis)也将 sessionId 设置到 response 的 header 中

// 保存 session
...
		/**
		 * Uses the {@link HttpSessionIdResolver} to write the session id to the response
		 * and persist the Session.
		 */
		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())) {
				// 将 sessionId 设置到 response 的 header 中
					SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this,
							this.response, sessionId);
				}
			}
		}
...
	@Override
	public void save(RedisSession session) {
		// 调用 RedisSession 的 saveDelta 持久化 Session
		session.saveDelta();
		// 如果 Session 为新创建,则发布一个 Session 创建的事件,监听这个事件的人们就知道来活儿啦
		// (用 Redis 做的 publish- subscribe)
		if (session.isNew()) {
			String sessionCreatedKey = getSessionCreatedChannel(session.getId());
			this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
			session.setNew(false);
		}
	}


		/**
		 * Saves any attributes that have been changed and updates the expiration of this
		 * session.
		 */
		private void saveDelta() {
			// 获取 UUID.randomUUID().toString() 产生的 sessionId
			String sessionId = getId();
			saveChangeSessionId(sessionId);
			// 创建 session 的时候,delta 不为空
			if (this.delta.isEmpty()) {
				return;
			}
			// key: spring:session:sessions:bac01f7a-10d4-4751-b638-1f5f225e935b
			// value: 
			// "lastAccessedTime" -> {Long@8202} 1660197001995
			// "maxInactiveInterval" -> {Integer@8204} 1800
			// "creationTime" -> {Long@8206} 1660196993128
			getSessionBoundHashOperations(sessionId).putAll(this.delta);
			String principalSessionKey = getSessionAttrNameKey(
					FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
			String securityPrincipalSessionKey = getSessionAttrNameKey(
					SPRING_SECURITY_CONTEXT);
			if (this.delta.containsKey(principalSessionKey)
					|| this.delta.containsKey(securityPrincipalSessionKey)) {
				if (this.originalPrincipalName != null) {
					String originalPrincipalRedisKey = getPrincipalKey(
							this.originalPrincipalName);
					RedisOperationsSessionRepository.this.sessionRedisOperations
							.boundSetOps(originalPrincipalRedisKey).remove(sessionId);
				}
				String principal = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
				this.originalPrincipalName = principal;
				if (principal != null) {
					String principalRedisKey = getPrincipalKey(principal);
					RedisOperationsSessionRepository.this.sessionRedisOperations
							.boundSetOps(principalRedisKey).add(sessionId);
				}
			}

			this.delta = new HashMap<>(this.delta.size());

			Long originalExpiration = (this.originalLastAccessTime != null)
					? this.originalLastAccessTime.plus(getMaxInactiveInterval())
							.toEpochMilli()
					: null;
			RedisOperationsSessionRepository.this.expirationPolicy
					.onExpirationUpdated(originalExpiration, this);
		}

创建完 Session 后,Redis 中存了哪些东东

  1. “spring:session:expirations:1660211460000”
    Redis Set 类型
    存储了类似这样的东西
    "“expires:90ab2766-1d1c-408d-b5ef-a23e54759ab9"”

    这个 k-v 存储这个Session 的 id,是一个 Set 类型的 Redis 数据结构。这个k中的最后的 1660198860000 值是一个时间戳,根据这个 Session 过期时刻滚动至下一分钟而计算得出。

  2. “spring:session:sessions:expires:90ab2766-1d1c-408d-b5ef-a23e54759ab9”
    Redis String 类型
    啥都没存

    这个 k-v 不存储任何有用数据,只是表示 Session 过期而设置。
    这个 k 在Redis中的过期时间即为 Session 的过期时间间隔,表示 Session 在 Redis 中的过期

  3. “spring:session:sessions:90ab2766-1d1c-408d-b5ef-a23e54759ab9”
    Redis Hash 类型
    存储了类似这样的东西
    “lastAccessedTime” = “1660209612961”
    “maxInactiveInterval” = “1800”
    “creationTime” = “1660209612960”

    这个 k-v 用来存储 Session 的详细信息,包括 Session 的创建时间,过期时间间隔、最近的访问时间、attributes等等。这个 k 的过期时间为 Session 的最大过期时间 + 5分钟。如果默认的最大过期时间为 30 分钟,则这个k的过期时间为 35 分钟

简单描述下,为什么 RedisSession 的存储用到了三个 Key,而非一个 Redis过期Key。 对于 Session 的实现,需要支持 HttpSessionEvent,即 Session创建、过期、销毁等事件。当监听到 Session 发生上述行为(事件)时,就会触发监听器做出相应的处理

spring-session 为了能够及时的产生 Session 的过期时的过期事件,所以增加了:
1) "spring:session:expirations:1660211460000"**
2) "spring:session:sessions:expires:90ab2766-1d1c-408d-b5ef-a23e54759ab9"

当 Session 在 Redis 失效后,再次请求

		@Override
		public HttpSessionWrapper getSession(boolean create) {
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			// 默认使用 cookie 策略,即从 cookies 中解析sessionId
			// 解析请求的 cookie,获取 session
			S requestedSession = getRequestedSession();
			// requestedSession 为 null,不走进这里边
			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 {
				// 如果根据 sessionId,没有获取到 session,则设置当前 request 属性,此sessionId 无效
				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
			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) {
				// resolveSessionIds 会调用 org.springframework.session.web.http.DefaultCookieSerializer#readCookieValues
				// 来解析 Cookie
				// SESSION=YmFjMDFmN2EtMTBkNC00NzUxLWI2MzgtMWY1ZjIyNWU5MzVi
				// Base64 解码出来后为:
				// sessionId = bac01f7a-10d4-4751-b638-1f5f225e935b
				List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
						.resolveSessionIds(this);
				for (String sessionId : sessionIds) {
					if (this.requestedSessionId == null) {
						this.requestedSessionId = sessionId;
					}
					// 解析出 sessionId 之后,拿这个 sessionId 去 Redis 里边查,封装成 RedisSession 返回
					S session = SessionRepositoryFilter.this.sessionRepository
							.findById(sessionId);
					if (session != null) {
						this.requestedSession = session;
						this.requestedSessionId = sessionId;
						break;
					}
				}
				this.requestedSessionCached = true;
			}
			// 查不到 Session,this.requestedSession = null
			return this.requestedSession;
		}

	@Override
	public List<String> readCookieValues(HttpServletRequest request) {
		Cookie[] cookies = request.getCookies();
		List<String> matchingCookieValues = new ArrayList<>();
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				// cookieName = SESSION
				// 解析 SESSION=YmFjMDFmN2EtMTBkNC00NzUxLWI2MzgtMWY1ZjIyNWU5MzVi
				// 默认使用 Base64 解码
				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;
	}

当 Session 在 Redis 没失效,再次请求时

那肯定是「续期」啦~
关键代码在这里

		@Override
		public HttpSessionWrapper getSession(boolean create) {
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			// 默认使用cookie策略,即从 cookies 中解析sessionId
			// 解析请求的 cookie,获取 session
			S requestedSession = getRequestedSession();
			// requestedSession 不为 null,不走进这里边
			if (requestedSession != null) {
				// 且当前 request 的 attribute 中的没有 session失效属性
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					// 则将 spring session 包装成 HttpSession
					// 并且设置「最近一次访问时间」为「当前时间」
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.setNew(false);
					// 并设置到当前 request 的 attribute 中
					// 防止同一个 request 去 getsession 时频繁的到存储器(如:Redis)中获取session,刷新 Redis 中的 Session
					setCurrentSession(currentSession);
					// 返回 session
					return currentSession;
				}
			}
			else {
				// 如果根据 sessionId,没有获取到 session,则设置当前 request 属性,认定此sessionId 无效
				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)"));
			}
			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) {
				// resolveSessionIds 会调用 org.springframework.session.web.http.DefaultCookieSerializer#readCookieValues
				// 来解析 Cookie
				// SESSION=YmFjMDFmN2EtMTBkNC00NzUxLWI2MzgtMWY1ZjIyNWU5MzVi
				// Base64 解码出来后为:
				// sessionId = bac01f7a-10d4-4751-b638-1f5f225e935b
				List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
						.resolveSessionIds(this);
				for (String sessionId : sessionIds) {
					if (this.requestedSessionId == null) {
						this.requestedSessionId = sessionId;
					}
					// 解析出 sessionId 之后,拿这个 sessionId 去 Redis 里边查,封装成 RedisSession 返回
					S session = SessionRepositoryFilter.this.sessionRepository
							.findById(sessionId);
					if (session != null) {
						// 将 session 赋值给 requestedSession,后续做返回用
						this.requestedSession = session;
						this.requestedSessionId = sessionId;
						break;
					}
				}
				this.requestedSessionCached = true;
			}
			// 返回查到的 Session
			return this.requestedSession;
		}


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

	/**
	 * Gets the session.
	 * @param id the session id
	 * @param allowExpired if true, will also include expired sessions that have not been
	 * deleted. If false, will ensure expired sessions are not returned.
	 * @return the Redis session
	 */
	private RedisSession getSession(String id, boolean allowExpired) {
		// 获取 k = spring:session:sessions:90ab2766-1d1c-408d-b5ef-a23e54759ab9
		Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
		// 得到 v =
		// "lastAccessedTime" = "1660209612961"
		// "maxInactiveInterval" = "1800"
		// "creationTime" = "1660209612960"
		if (entries.isEmpty()) {
			return null;
		}
		// 包装成 MapSession
		MapSession loaded = loadSession(id, entries);
		// 判断该 Session 是否过期
		// loaded.isExpired()
		if (!allowExpired && loaded.isExpired()) {
			// 过期的话,session 为 null 返回
			return null;
		}
		// 用 MapSession 再包装成 RedisSession 返回
		RedisSession result = new RedisSession(loaded);
		result.originalLastAccessTime = loaded.getLastAccessedTime();
		return result;
	}

	boolean isExpired(Instant now) {
		// 如果 maxInactiveInterval 配置的是负数
		// 则永远不过期
		if (this.maxInactiveInterval.isNegative()) {
			return false;
		}
		// 如果为正数,与当前时间判断是否过期
		return now.minus(this.maxInactiveInterval).compareTo(this.lastAccessedTime) >= 0;
	}

版权声明:本文为weixin_42247675原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。