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 中存了哪些东东
-
“spring:session:expirations:1660211460000”
Redis Set 类型
存储了类似这样的东西
"“expires:90ab2766-1d1c-408d-b5ef-a23e54759ab9"”
这个 k-v 存储这个Session 的 id,是一个 Set 类型的 Redis 数据结构。这个k中的最后的 1660198860000 值是一个时间戳,根据这个 Session 过期时刻滚动至下一分钟而计算得出。 -
“spring:session:sessions:expires:90ab2766-1d1c-408d-b5ef-a23e54759ab9”
Redis String 类型
啥都没存
这个 k-v 不存储任何有用数据,只是表示 Session 过期而设置。
这个 k 在Redis中的过期时间即为 Session 的过期时间间隔,表示 Session 在 Redis 中的过期 -
“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;
}