2.2SpringSession源码:createSession()

2.2SpringSession源码:createSession()

Posted by ZhaoLe on May 7, 2019

创建session具体实现是在RedisOperationsSessionRepository

1
2
3
4
5
6
7
8
9
@Override
public RedisSession createSession() {
	RedisSession redisSession = new RedisSession();
	if (this.defaultMaxInactiveInterval != null) {
		redisSession.setMaxInactiveInterval(
				Duration.ofSeconds(this.defaultMaxInactiveInterval));
	}
	return redisSession;
}
  • 【3】new出一个RedisSession对象,简简单单一行,里面的学问倒是挺多的。
1
2
3
4
5
6
7
8
9
//RedisSession.java
RedisSession() {
	this(new MapSession());
	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());
	this.isNew = true;
	this.flushImmediateIfNecessary();
}
1
2
3
4
5
6
//RedisSession.java
private void flushImmediateIfNecessary() {
	if (RedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) {
		saveDelta();
	}
}

flushImmediateIfNecessary方法之前,先要知道在SpringSession中将数据写入Redis实例有两种模式ON_SAVE(默认)和IMMEDIATE,可以再application.properties文件中配置,例如spring.session.redis.flush-mode=immediate,这两种的区别在于:

  • ON_SAVE模式:只有当主动调用的时候(SessionRepository#save)才会被触发,在web环境中通常是在提交HTTP响应的时候完成,所以它是默认模式
  • IMMEDIATE模式:尽可能快的写入redis,在创建新的session时候就调用(SessionRepository#createSession())或者在session上设置attribute的时候也是会立刻写入redis

所以在默认情况下this.flushImmediateIfNecessary中的saveDelta不会被执行到,但是最后在commit阶段会执行保存动作,所以我们就放在这边一起看了,saveDelta方法保存已更改的任何属性,并更新此session的到期日期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void saveDelta() {
	String sessionId = getId();
	saveChangeSessionId(sessionId);
	if (this.delta.isEmpty()) {
		return;
	}
	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);
}

RedisSession中维护着一个cache,是属于MapSession类型,里面存放着最新跟session有关的基本信息例如creationTime,lastAccessedTime和所有的sessionAttrs等。RedisSession本身也有一些成员变量如originalLastAccessTimeoriginalSessionIddelta等等,其中delta是个Map类型,用来存放当前一些变更的对象(delta符号在数学公式里就表示变化的量),默认的取值是从cache来的

  • 【2】获取cache中的id
  • 【3】将cache中的id和原始的originalSessionId进行比较,如果不同并且不是新创建的session,就会将 originalSessionIdoriginalExpiredKey给替换掉。
  • 【7】根据sessionId将delta的值持久化写入redis
  • 【9-26】获取sessionAttr属性PRINCIPAL_NAME_INDEX_NAMESPRING_SECURITY_CONTEXT并且对其处理,本质上也是做替换。
  • 【28】已经处理+持久化完delta里面的参数了,可以进行初始化。
  • 【30-31】刷新下过期时间,直接拿最后访问时间+最大过期间隔(默认30分钟)
  • 【32-33】SpringSession有自己的过期时间处理策略,//TODO….