7. Spring Session 源码分析-http请求
笔者在上篇文章中分析了Spring Session 启动时容器初始化了哪些组件, 这篇文章来分析当有请求过来时, Spring Session 的处理流程.
1. SessionRepositoryFilter
笔者前面说了, SessionRepositoryFilter 是个过滤器, 会拦截所有的Http 请求, 所以当有http 请求时,首先会执行该过滤器的doFilterInbternal 方法:
- 包装元素的HttpRequest 和 HttpResponse 对象
调用ApplicationFilterChain 的doFilter 方法, doFilter 调用internalDoFilter 方法, internalDoFilter 方法执行逻辑:
- 依次执行过滤器链的doFilter方法
过滤链执行完之后, 准备通过反射执行目标方法, 即接口方法. 在执行目标方法之前开始, 会执行两次 SessionRepositoryRequestWrapper#getSession(boolean) 方法
- 第一次执行, 传入参数为false, 尝试从缓存中获取session
- 第二次执行, 传入参数为true, 尝试创建session
提交session, 将Session中的数据同步到redis 中
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
// 1. 封装请求响应对象
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response);
try {
// 2. 执行过滤器链, 过滤器链执行完后, 执行目标接口方法
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
// 3. 提交Session, 将Session中的数据同步到redis中
wrappedRequest.commitSession();
}
}
2. 执行过滤器链, 并执行目标方法
2.1 获取session: SessionRepositoryRequestWrapper#getSession(boolean)
这个方法应该算是spring session最核心的方法了. 每一次请求, 在目标接口调用之前都会调用这个方法两次, 第一次传入参数为false, 第二次传入参数为true.
第一次:getSession(false):
- 从request attributes中通过CURRENT_SESSION_ATTR 获取当前session, 肯定为null
尝试从缓存中获取session:
- 如果第一次登录的话, 返回null, 然后结束方法, return null
- 第二次登录的话, 也就是说redis 中已经有了session的缓存信息, 会返回session对象. 然后封装获取到的session对象, 存入request 的attribute中
第一次:getSession(true):
从request attributes中通过CURRENT_SESSION_ATTR 获取当前session:
- 如果是第二次登录的话, 那么此时可以获取到session 对象, 直接返回.
如果是第一次登录的话, 此时获取到的依然为null. 然后继续向下执行
- 从redis 缓存中获取session对象, 因为时第一次执行, 所以此时获取到的依然为null
- 由于传入参数为true, 所以开始创建session对象:
- 创建对象之后, 将session 存入request attributes中, 以便后续获取.
@Override
public HttpSessionWrapper getSession(boolean create) {// 从request 中attributes 中获取session, key为: SessionRepositoryFilter.CURRENT_SESSION_ATTR
HttpSessionWrapper currentSession = getCurrentSession();
// 如果获取到session, 则直接返回
if (currentSession != null) {
return currentSession;
}
// 尝试从缓存中获取当前session, 根据配置的SessionId 解析器来决定从Cookie中解析SessionId还是从Header的x-Auth-Token 中解析SessionId
S requestedSession = getRequestedSession();
// 如果从缓存中获取到了Session
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
// 设置session 的属性信息, 并封装为HttpSessionWrapper 对象
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
currentSession.setNew(false);
// 保存当前session到request的attribute 属性中, key 为: SessionRepositoryFilter.CURRENT_SESSION_ATTR , 获取再次获取时, 在第一步就能获取到session了
setCurrentSession(currentSession);
return currentSession;
}
}
else {
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
// 如果从request和从缓存中都获取不到session, 并且传入的create 为false, 那么则直接返回null
if (!create) {
return null;
}
// 如果前面两步获取不懂session, 并且传入的create 为true, 那么开始创建session
// 创建一个RedisSession 对象, 并设置过期时间:maxInactiveInterval
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
// 将RedisSession 对象封装为HttpSessionWrapper 对象
currentSession = new HttpSessionWrapper(session, getServletContext());
// 保存当前session到request的attribute 属性中, key 为: SessionRepositoryFilter.CURRENT_SESSION_ATTR , 获取再次获取时, 在第一步就能获取到session了
setCurrentSession(currentSession);
return currentSession;
}
2.2 从缓存中获取session对象:SessionRepositoryRequestWrapper.getRequestedSession
解析请求中的sessionId, 根据配置的SessionId解析器的不同, 解析方式也不同:
- CookieHttpSessionIdResolver: 从请求携带的Cookie中寻找名为SESSION的值, 这个时浏览器携带的, 所以都会有的. 然后进行base64解码, 解码的结果就是sessionId
- HeaderHttpSessionIdResolver: 从请求头的header中寻找, 寻找名为x-Auth-Token 的header, 寻找的值就是sessionId
通过sessionId从缓存中获取session对象
private S getRequestedSession() {
// 第一次获取, requestedSessionCached 肯定为false
if (!this.requestedSessionCached) {
// 1. 获取sessionId, 根据解析器类型不同, 获取方式不同
List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);
for (String sessionId : sessionIds) {
if (this.requestedSessionId == null) {
this.requestedSessionId = sessionId;
}
// 通过sessionId从缓存中获取尝试获取session对象
S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);
// 如果获取到了session, 则返回session
if (session != null) {
this.requestedSession = session;
this.requestedSessionId = sessionId;
break;
}
}
// 设置是否已尝试从缓存中读取session, 状态标识为true
this.requestedSessionCached = true;
}
return this.requestedSession;
}
3. 提交Session, 将Session中的数据同步到redis中
当接口方法执行完之后, 将session信息刷新到redis中, 同时将sessionid 回写到response对象中:
- 从request中获取当前的session
判断session是否为null:
- 如果session 为null, 则设置session过期
如果session 不为null, 则继续处理:
- 清理当前request 中存储的session 相关的信息
- 将session中存储的数据全部刷新到redis中
获取sessionId, 将sessionid回写到Response中. 根据SessionId解析器的不同, 写入的位置也不同:
- HeaderHttpSessionIdResolver: 写入响应头中, headerName 为 x-Auth-Token
- CookieHttpSessionIdResolver: 写入响应头中, headerName 为 Set-Cookie: SESSION=xxxx; 需要注意的是, response 的header只有一个, headerName为Set-Cookie, 需要自行解析.如: Set-Cookie:SESSION=M2ZlNzA3YjItNmQzZi00YjkzLThmNjItYTI3MzY5ODQzYzJl;xxxx
private void commitSession() {
// 从request中获取当前session
HttpSessionWrapper wrappedSession = getCurrentSession();
// 如果获取的session为null, 则设置session过期
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
}
else {
S session = wrappedSession.getSession();
// 清理操作, 将requestedSessionId, requestedSession重置为null, requestedSessionCached 设置为false
clearRequestedSessionCache();
// 将session 信息存储到redis 中
SessionRepositoryFilter.this.sessionRepository.save(session);
// 获取sessionId
String sessionId = session.getId();
if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) {
// 将session回写到Response中. 不同的SessionId 解析器写入的位置不同
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
}
}
}
还没有评论,来说两句吧...