SpringSession源码解析

spring session 关键组件的UML图

SpringSession源码解析_第1张图片
image.png
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        //在request的属性中放入它的“session仓库”
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        //将httprequest包装一下
        SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
                request, response, this.servletContext);
        //将response添加多session的相关信息
        SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
                wrappedRequest, response);
        //将这个处理器本身放入Request的属性中
        HttpServletRequest strategyRequest = this.httpSessionStrategy
                .wrapRequest(wrappedRequest, wrappedResponse);
        //包装成多session的Response
        HttpServletResponse strategyResponse = this.httpSessionStrategy
                .wrapResponse(wrappedRequest, wrappedResponse);
        //执行过滤器链,这样后续的所有web组件得到的都是被包装过的Request和response了
        try {
            filterChain.doFilter(strategyRequest, strategyResponse);
        }
         //无论直接结果如何,都将我们的mock的session放入Response,并将此session存入我们的SessionRepository.
        finally {
            wrappedRequest.commitSession();
        }
    }

CookieHttpSessionStrategy

其主要作用就是利用Cookie去实现spring“自制session”的相关方法。

默认的CookieSerializer,目前就只有一个默认实现
public class DefaultCookieSerializer implements CookieSerializer {
        //默认spring-session往Cookie中写入的代表写session的ID名,作用类似web容器默认的“jsessionId”
    private String cookieName = "SESSION";

    private Boolean useSecureCookie;

    private boolean useHttpOnlyCookie = isServlet3();

    private String cookiePath;

    private int cookieMaxAge = -1;

    private String domainName;

    private Pattern domainNamePattern;
    //从set方法可知,这个值的作用是,如果你想追踪session值是在哪个jvm上写入的(比如日志追踪),可以设置它,一般没什么用。
    private String jvmRoute;

    private boolean useBase64Encoding;

    private String rememberMeRequestAttribute;

    //将request里面的所有Cookie取出,筛选出“SESSION开头的spring session需要的Cookie值,根据需要切掉JvmRoute和解码,返回”
    public List readCookieValues(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        List matchingCookieValues = new ArrayList();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                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;
    }

    
    public void writeCookieValue(CookieValue cookieValue) {
        HttpServletRequest request = cookieValue.getRequest();
        HttpServletResponse response = cookieValue.getResponse();

        String requestedCookieValue = cookieValue.getCookieValue();
        //取出要设置的放入session中的值,如果设置了jvmRoute,在末尾拼接上。
        String actualCookieValue = this.jvmRoute == null ? requestedCookieValue
                : requestedCookieValue + this.jvmRoute;
        //创建我们要代替jsessionId的cookie,如果设置useBase64Encoding了,还会将SESSION键的值进行base64编码。
        Cookie sessionCookie = new Cookie(this.cookieName, this.useBase64Encoding
                ? base64Encode(actualCookieValue) : actualCookieValue);
       //根据request的设置设置cookie是否必须使用https协议
        sessionCookie.setSecure(isSecureCookie(request));
        //设置cookie的path
        sessionCookie.setPath(getCookiePath(request));
        //根据request的domain设置cookie
        String domainName = getDomainName(request);
        if (domainName != null) {
            sessionCookie.setDomain(domainName);
        }
        //设置是否只供http协议
        if (this.useHttpOnlyCookie) {
            sessionCookie.setHttpOnly(true);
        }
        //如果要写的值是空的,Cookie不保存
        if ("".equals(requestedCookieValue)) {
            sessionCookie.setMaxAge(0);
        }
        //有spring security 实现记住我的功能需要的属性键值对时,cookie永久保存
        else if (this.rememberMeRequestAttribute != null
                && request.getAttribute(this.rememberMeRequestAttribute) != null) {
            // the cookie is only written at time of session creation, so we rely on
            // session expiration rather than cookie expiration if remember me is enabled
            sessionCookie.setMaxAge(Integer.MAX_VALUE);
        }
        else {
            sessionCookie.setMaxAge(this.cookieMaxAge);
        }
        //包含“SESSION=XXXX”的Cookie值放入Response最后~
        response.addCookie(sessionCookie);
    }
    ……忽略其他辅助方法
}

所以DefaultCookieSerializer的主要作用就是操作Cookie的读写,应为是并不是依赖原来的jsessionId所以,特别写了这个CookieSerializer。

CookieHttpSessionStrategy

这个实现了HttpSessionStrategy和RequestResponsePostProcessor2个接口的功能。

  1. HttpSessionStrategy的3个接口
    1. getRequestedSessionId 获取sessionId 既获取spring session设置在Cookie中的键SESSION的值,既spring session的ID。这里的读取利用了CookieSerializer工具类的读取方法。
    2. onNewSession 在session被创建时被调用,作用???
    3. onInvalidateSession 想要删除session时调用的方法
  2. HttpSessionManager
    1. getCurrentSessionAlias
      就是取得当前session的别名,如果你没有用多session,默认就是0。
      如果你的url里面传入了查询参数_s=数字,就会返回这个数字别名。
      比如"mockUrl?_s=2"那么,返回的就是2。
    2. getSessionIds
      找到SESSION里面所有的session别名作为key,sessionId作为value返回。
    3. encodeURL
      ??????将url和别名拼接在一起
    4. getNewSessionAlias
      返回一个新的session别名,阅读源码可以知道其实就是在原来的别名基础上+1,比如你已经在SESSSION 键里面有 0 12312012-2321-23,别名就是0了,现在新增一个别名,就是返回1.
同一个浏览器,支持多个session
SessionRepositoryFilter
SessionRepositoryRequestWrapper

这个类就是用来代替容器自身HttpServletRequest的实现。这里有2个方法是重点。

  1. getSession 获取session的方法,显然这个方法会被重写。
      @Override
      public HttpSessionWrapper getSession(boolean create) {
          //getCurrentSession的逻辑就是去request里面查找有没有Session相关的键值对:SessionRepository.CURRENT_SESSION=HttpSessionWrapper。
          //有的话说明此Request已经有session了返回即可。
          HttpSessionWrapper currentSession = getCurrentSession();
          if (currentSession != null) {
              return currentSession;
          }
          //通过HttpSessionStrategy提供的功能去查找当前Request是否有COOKIE值SESSION=XXXX,得到sessionId。
          String requestedSessionId = getRequestedSessionId();
          //如果有,并且request中没有SessionRepository.invalidSessionId这个键,则通过sessionRepository去查出存储的session,
          //将spring的session包装成HttpSessionWrapper,放入Request中。
          //既键值对:SessionRepository.CURRENT_SESSION=HttpSessionWrapper
          //这里如果没能通过此sessionId从SessionRepository中找出对应的session,则设置SessionRepository.invalidSessionId为true,下次同样的sessionId就别找了。
          if (requestedSessionId != null
                  && getAttribute(INVALID_SESSION_ID_ATTR) == null) {
              S session = getSession(requestedSessionId);
              if (session != null) {
                  this.requestedSessionIdValid = true;
                  currentSession = new HttpSessionWrapper(session, getServletContext());
                  currentSession.setNew(false);
                  setCurrentSession(currentSession);
                  return currentSession;
              }
              else {
                  // This is an invalid session id. No need to ask again if
                  // request.getSession is invoked for the duration of this request
                  if (SESSION_LOGGER.isDebugEnabled()) {
                      SESSION_LOGGER.debug(
                              "No session found by id: Caching result for getSession(false) for 、 HttpServletRequest.");
                  }
                  setAttribute(INVALID_SESSION_ID_ATTR, "true");
              }
          }
          //上面的逻辑就是通过request里面header头里的sessionId去查找对应的Session的逻辑。如果create设置为false,那到这就完了。下面是创建新session的逻辑。
          if (!create) {
              return null;
          }
          //debug日志记录
          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)"));
          }
           //通过SessionRepository创建新的session,包装成HttpSessionWrapper,同样将此Session设置到Request的属性中,方便下次使用。
          S session = SessionRepositoryFilter.this.sessionRepository.createSession();
          session.setLastAccessedTime(System.currentTimeMillis());
          currentSession = new HttpSessionWrapper(session, getServletContext());
          setCurrentSession(currentSession);
          return currentSession;
          //注意这整个getSession的逻辑里面没有涉及到session最后的持久化,这个逻辑实在commitSession中完成的。
      }

  1. commitSession 在SessionRepositoryFilter拦截器执行的最后会被执行。因为这方法并不打算给用户使用,所以是private的。
        private void commitSession() {
            HttpSessionWrapper wrappedSession = getCurrentSession();
           //从request里面拿出存的session,如果没有或者此sessionId已被标记为失效,就调用HttpSessionStrategy#onInvalidateSession.
           //主要作用是设置Response的Cookie,删除失效的sessionId。
            if (wrappedSession == null) {
                if (isInvalidateClientSession()) {
                    SessionRepositoryFilter.this.httpSessionStrategy
                            .onInvalidateSession(this, this.response);
                }
            }
            //如果有session,保存到SessionRepository中
            //接着判断这个Session是否是request请求来的时候的那个session,如果不是,说明是新的session。
            //调用HttpSessionStrategy#onNewSession,将此sessionId加入Cookie值中。
            else {
                S session = wrappedSession.getSession();
                SessionRepositoryFilter.this.sessionRepository.save(session);
                if (!isRequestedSessionIdValid()
                        || !session.getId().equals(getRequestedSessionId())) {
                    SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
                            this, this.response);
                }
            }
        }
SpringBoot中使用spring-session

如果没有使用springboot,我们需要自己去初始化SessionRepositoryFilter。如果使用了SpringBoot。比如用Redis做为SessionRepository存储session。
在注册文件中引入@EnableRedisHttpSession注解即可。这个注解的实际作用就是引入RedisHttpSessionConfiguration这个预先写好的配置类。其继承于SpringHttpSessionConfiguration。
观察SpringHttpSessionConfiguration的源码可得,它的主要作用就是初始化我们上面谈到的那些组件,CookieSerializer,HttpSessionStrategy等。这是所有SessionRepository实现都需要的组件,所以放在基类中。

你可能感兴趣的:(SpringSession源码解析)