7. Spring Session 源码分析-http请求

我不是女神ヾ 2022-02-18 17:01 284阅读 0赞

笔者在上篇文章中分析了Spring Session 启动时容器初始化了哪些组件, 这篇文章来分析当有请求过来时, Spring Session 的处理流程.

1. SessionRepositoryFilter

笔者前面说了, SessionRepositoryFilter 是个过滤器, 会拦截所有的Http 请求, 所以当有http 请求时,首先会执行该过滤器的doFilterInbternal 方法:

  1. 包装元素的HttpRequest 和 HttpResponse 对象
  2. 调用ApplicationFilterChain 的doFilter 方法, doFilter 调用internalDoFilter 方法, internalDoFilter 方法执行逻辑:

    1. 依次执行过滤器链的doFilter方法
    2. 过滤链执行完之后, 准备通过反射执行目标方法, 即接口方法. 在执行目标方法之前开始, 会执行两次 SessionRepositoryRequestWrapper#getSession(boolean) 方法

      1. 第一次执行, 传入参数为false, 尝试从缓存中获取session
      2. 第二次执行, 传入参数为true, 尝试创建session
  3. 提交session, 将Session中的数据同步到redis 中

    @Override
    protected void doFilterInternal(HttpServletRequest request,

    1. HttpServletResponse response, FilterChain filterChain)
    2. throws ServletException, IOException {
    3. request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
    4. // 1. 封装请求响应对象
    5. SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext);
    6. SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response);
    7. try {
    8. // 2. 执行过滤器链, 过滤器链执行完后, 执行目标接口方法
    9. filterChain.doFilter(wrappedRequest, wrappedResponse);
    10. }
    11. finally {
    12. // 3. 提交Session, 将Session中的数据同步到redis中
    13. wrappedRequest.commitSession();
    14. }

    }

2. 执行过滤器链, 并执行目标方法

2.1 获取session: SessionRepositoryRequestWrapper#getSession(boolean)

这个方法应该算是spring session最核心的方法了. 每一次请求, 在目标接口调用之前都会调用这个方法两次, 第一次传入参数为false, 第二次传入参数为true.

第一次:getSession(false):

  1. 从request attributes中通过CURRENT_SESSION_ATTR 获取当前session, 肯定为null
  2. 尝试从缓存中获取session:

    • 如果第一次登录的话, 返回null, 然后结束方法, return null
    • 第二次登录的话, 也就是说redis 中已经有了session的缓存信息, 会返回session对象. 然后封装获取到的session对象, 存入request 的attribute中

第一次:getSession(true):

  1. 从request attributes中通过CURRENT_SESSION_ATTR 获取当前session:

    • 如果是第二次登录的话, 那么此时可以获取到session 对象, 直接返回.
    • 如果是第一次登录的话, 此时获取到的依然为null. 然后继续向下执行

      1. 从redis 缓存中获取session对象, 因为时第一次执行, 所以此时获取到的依然为null
      2. 由于传入参数为true, 所以开始创建session对象:
      3. 创建对象之后, 将session 存入request attributes中, 以便后续获取.

    @Override
    public HttpSessionWrapper getSession(boolean create) {

    1. // 从request 中attributes 中获取session, key为: SessionRepositoryFilter.CURRENT_SESSION_ATTR
    2. HttpSessionWrapper currentSession = getCurrentSession();
    3. // 如果获取到session, 则直接返回
    4. if (currentSession != null) {
    5. return currentSession;
    6. }
    7. // 尝试从缓存中获取当前session, 根据配置的SessionId 解析器来决定从Cookie中解析SessionId还是从Header的x-Auth-Token 中解析SessionId
    8. S requestedSession = getRequestedSession();
    9. // 如果从缓存中获取到了Session
    10. if (requestedSession != null) {
    11. if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
    12. // 设置session 的属性信息, 并封装为HttpSessionWrapper 对象
    13. requestedSession.setLastAccessedTime(Instant.now());
    14. this.requestedSessionIdValid = true;
    15. currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
    16. currentSession.setNew(false);
    17. // 保存当前session到request的attribute 属性中, key 为: SessionRepositoryFilter.CURRENT_SESSION_ATTR , 获取再次获取时, 在第一步就能获取到session了
    18. setCurrentSession(currentSession);
    19. return currentSession;
    20. }
    21. }
    22. else {
    23. setAttribute(INVALID_SESSION_ID_ATTR, "true");
    24. }
    25. // 如果从request和从缓存中都获取不到session, 并且传入的create 为false, 那么则直接返回null
    26. if (!create) {
    27. return null;
    28. }
    29. // 如果前面两步获取不懂session, 并且传入的create 为true, 那么开始创建session
    30. // 创建一个RedisSession 对象, 并设置过期时间:maxInactiveInterval
    31. S session = SessionRepositoryFilter.this.sessionRepository.createSession();
    32. session.setLastAccessedTime(Instant.now());
    33. // 将RedisSession 对象封装为HttpSessionWrapper 对象
    34. currentSession = new HttpSessionWrapper(session, getServletContext());
    35. // 保存当前session到request的attribute 属性中, key 为: SessionRepositoryFilter.CURRENT_SESSION_ATTR , 获取再次获取时, 在第一步就能获取到session了
    36. setCurrentSession(currentSession);
    37. return currentSession;

    }

2.2 从缓存中获取session对象:SessionRepositoryRequestWrapper.getRequestedSession

  1. 解析请求中的sessionId, 根据配置的SessionId解析器的不同, 解析方式也不同:

    • CookieHttpSessionIdResolver: 从请求携带的Cookie中寻找名为SESSION的值, 这个时浏览器携带的, 所以都会有的. 然后进行base64解码, 解码的结果就是sessionId
    • HeaderHttpSessionIdResolver: 从请求头的header中寻找, 寻找名为x-Auth-Token 的header, 寻找的值就是sessionId
  2. 通过sessionId从缓存中获取session对象

    private S getRequestedSession() {

    1. // 第一次获取, requestedSessionCached 肯定为false
    2. if (!this.requestedSessionCached) {
    3. // 1. 获取sessionId, 根据解析器类型不同, 获取方式不同
    4. List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);
    5. for (String sessionId : sessionIds) {
    6. if (this.requestedSessionId == null) {
    7. this.requestedSessionId = sessionId;
    8. }
    9. // 通过sessionId从缓存中获取尝试获取session对象
    10. S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);
    11. // 如果获取到了session, 则返回session
    12. if (session != null) {
    13. this.requestedSession = session;
    14. this.requestedSessionId = sessionId;
    15. break;
    16. }
    17. }
    18. // 设置是否已尝试从缓存中读取session, 状态标识为true
    19. this.requestedSessionCached = true;
    20. }
    21. return this.requestedSession;

    }

3. 提交Session, 将Session中的数据同步到redis中

当接口方法执行完之后, 将session信息刷新到redis中, 同时将sessionid 回写到response对象中:

  1. 从request中获取当前的session
  2. 判断session是否为null:

    • 如果session 为null, 则设置session过期
    • 如果session 不为null, 则继续处理:

      1. 清理当前request 中存储的session 相关的信息
      2. 将session中存储的数据全部刷新到redis中
      3. 获取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() {

    1. // 从request中获取当前session
    2. HttpSessionWrapper wrappedSession = getCurrentSession();
    3. // 如果获取的session为null, 则设置session过期
    4. if (wrappedSession == null) {
    5. if (isInvalidateClientSession()) {
    6. SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
    7. }
    8. }
    9. else {
    10. S session = wrappedSession.getSession();
    11. // 清理操作, 将requestedSessionId, requestedSession重置为null, requestedSessionCached 设置为false
    12. clearRequestedSessionCache();
    13. // 将session 信息存储到redis 中
    14. SessionRepositoryFilter.this.sessionRepository.save(session);
    15. // 获取sessionId
    16. String sessionId = session.getId();
    17. if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) {
    18. // 将session回写到Response中. 不同的SessionId 解析器写入的位置不同
    19. SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
    20. }
    21. }

    }

发表评论

表情:
评论列表 (有 0 条评论,284人围观)

还没有评论,来说两句吧...

相关阅读