SpringSession 集成redis之后是如何实现的session有效期自动延长的呢?
首先我们先在自己的springboot项目中集成springsession + redis,只需要在springboot启动类加
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 600)
这行注释就可以,上面的注释默认设置springsession在redis中的有效时长为10分钟,那么接下来就让我们跟随源码去了解一下,springsession在redis中的有效期是如何自动延时的
HttpSession session = request.getSession();
session.setAttribute(RedisConstants.USER_SAVE_NAME, sysUser.getUsername());
以这两句代码为例,我在controller中获取request中的session,然后在session中设置了一个参数
通过debug模式,F5进入setAttribute方法
org.springframework.session.web.http.HttpSessionAdapter类
public void setAttribute(String name, Object value) {
this.checkState();
Object oldValue = this.session.getAttribute(name);
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener)oldValue).valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
} catch (Throwable var6) {
logger.error("Error invoking session binding event listener", var6);
}
}
if (value instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this, name, value));
} catch (Throwable var5) {
logger.error("Error invoking session binding event listener", var5);
}
}
}
}
源码如上,然后在划线部分继续F5跟进
org.springframework.session.data.redis.RedisOperationsSessionRepository
public void setAttribute(String attributeName, Object attributeValue) {
this.cached.setAttribute(attributeName, attributeValue);
this.putAndFlush(RedisOperationsSessionRepository.getSessionAttrNameKey(attributeName), attributeValue);
}
进入RedisOperationsSessionRepository类 的setAttribute方法,发现他在将我们设置参数保存之后,执行了一个putAndFlush方法,继续跟进
org.springframework.session.data.redis.RedisOperationsSessionRepository
private void putAndFlush(String a, Object v) {
this.delta.put(a, v);
this.flushImmediateIfNecessary();
}
还是该类中的方法,再进入flushImmediateIfNecessary方法,发现有一个save方法,我们的session有效时长顺延就是在这个方法中做的
public void save(RedisOperationsSessionRepository.RedisSession session) {
session.save();
if (session.isNew()) {
String sessionCreatedKey = this.getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.setNew(false);
}
}
private void save() {
this.saveChangeSessionId();
this.saveDelta();
}
private void saveDelta() {
if (!this.delta.isEmpty()) {
String sessionId = this.getId();
RedisOperationsSessionRepository.this.getSessionBoundHashOperations(sessionId).putAll(this.delta);
String principalSessionKey = RedisOperationsSessionRepository.getSessionAttrNameKey(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
String securityPrincipalSessionKey = RedisOperationsSessionRepository.getSessionAttrNameKey("SPRING_SECURITY_CONTEXT");
if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) {
String principal;
if (this.originalPrincipalName != null) {
principal = RedisOperationsSessionRepository.this.getPrincipalKey(this.originalPrincipalName);
RedisOperationsSessionRepository.this.sessionRedisOperations.boundSetOps(principal).remove(new Object[]{sessionId});
}
principal = RedisOperationsSessionRepository.PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
this.originalPrincipalName = principal;
if (principal != null) {
String principalRedisKey = RedisOperationsSessionRepository.this.getPrincipalKey(principal);
RedisOperationsSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey).add(new Object[]{sessionId});
}
}
this.delta = new HashMap(this.delta.size());
Long originalExpiration = this.originalLastAccessTime != null ? this.originalLastAccessTime.plus(this.getMaxInactiveInterval()).toEpochMilli() : null;
RedisOperationsSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this);
}
}
public void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) {
String keyToExpire = "expires:" + session.getId();
long toExpire = roundUpToNextMinute(expiresInMillis(session));
long sessionExpireInSeconds;
String sessionKey;
if (originalExpirationTimeInMilli != null) {
sessionExpireInSeconds = roundUpToNextMinute(originalExpirationTimeInMilli);
if (toExpire != sessionExpireInSeconds) {
sessionKey = this.getExpirationKey(sessionExpireInSeconds);
this.redis.boundSetOps(sessionKey).remove(new Object[]{keyToExpire});
}
}
sessionExpireInSeconds = session.getMaxInactiveInterval().getSeconds();
sessionKey = this.getSessionKey(keyToExpire);
if (sessionExpireInSeconds < 0L) {
this.redis.boundValueOps(sessionKey).append("");
this.redis.boundValueOps(sessionKey).persist();
this.redis.boundHashOps(this.getSessionKey(session.getId())).persist();
} else {
String expireKey = this.getExpirationKey(toExpire);
BoundSetOperations<Object, Object> expireOperations = this.redis.boundSetOps(expireKey);
expireOperations.add(new Object[]{keyToExpire});
long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5L);
expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
if (sessionExpireInSeconds == 0L) {
this.redis.delete(sessionKey);
} else {
this.redis.boundValueOps(sessionKey).append("");
this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds, TimeUnit.SECONDS);
}
this.redis.boundHashOps(this.getSessionKey(session.getId())).expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
}
}
上面的onExpirationUpdated方法实现的就是延时功能,但是save方法其实并不是在此处调用的,而是
org.springframework.session.web.http.SessionRepositoryFilter类中
private void commitSession() {
SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper.HttpSessionWrapper wrappedSession = this.getCurrentSession();
if (wrappedSession == null) {
if (this.isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
} else {
S session = wrappedSession.getSession();
this.clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.save(session);
String sessionId = session.getId();
if (!this.isRequestedSessionIdValid() || !sessionId.equals(this.getRequestedSessionId())) {
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
}
}
}
这个commitSession()方法中调用的,具体链路这里就不贴了,太深,有兴趣的同学可以自己dubug跟一下
那么最后我们宏观整理一下整个链路,就是EnableRedisHttpSession引用的这个标签中,引入了下面这个类,
@Import({RedisHttpSessionConfiguration.class}),该类中注入了
@Bean
public RedisOperationsSessionRepository sessionRepository() {...}
这个bean,而这个bean中又new了 RedisSessionExpirationPolicy 这个类的对象,然后在这个类中实现了onExpirationUpdated这个方法,具体这个方法的调用其实是通过过滤器在对session做操作后,通过commitSession方法调用到延时方法的,由此实现了session在redis中的有效时长自动延时的功能。