spring-session简介及实现原理源码分析

一:spring-session介绍

1.简介

session一直都是我们做集群时需要解决的一个难题,过去我们可以从serlvet容器上解决,比如开源servlet容器-tomcat提供的tomcat-redis-session-manager、memcached-session-manager。

或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上..

但是这两种办法都存在弊端。

spring-session是spring旗下的一个项目,把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题。可简单快速且无缝的集成到我们的应用中。

2.支持功能

1)轻易把session存储到第三方存储容器,框架提供了redis、jvm的map、mongo、gemfire、hazelcast、jdbc等多种存储session的容器的方式。

2)同一个浏览器同一个网站,支持多个session问题。

3)RestfulAPI,不依赖于cookie。可通过header来传递jessionID

4)WebSocket和spring-session结合,同步生命周期管理。

3.集成方式

集成方式非常简单,直接看官网的samplesandguide。http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/

主要分为以下几个集成步骤:

1)引入依赖jar包

2)注解方式或者xml方式配置特定存储容器的存储方式,如redis的xml配置方式

<context:annotation-config/>
/** 初始化一切spring-session准备,且把springSessionFilter放入IOC     **/
<beanclass="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
/** 这是存储容器的链接池 **/
<beanclass="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>

3)xml方式配置 web.xml ,配置 springSessionFilter到 filter chain中

<filter>
     <filter-name>springSessionRepositoryFilter</filter-name>
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
   </filter>
   <filter-mapping>
     <filter-name>springSessionRepositoryFilter</filter-name>
     <url-pattern>/*</url-pattern>
     <dispatcher>REQUEST</dispatcher><dispatcher>ERROR</dispatcher>
   </filter-mapping>

二:spring-session框架内部剖析

1.框架高层抽象结构图

2.spring-session重写servlet request 及 redis实现存储相关问题

spring-session无缝替换应用服务器的request大概原理是:
1.自定义个Filter,实现doFilter方法
2.继承 HttpServletRequestWrapper 、HttpServletResponseWrapper 类,重写getSession等相关方法(在这些方法里调用相关的 session存储容器操作类)。
3.在 第一步的doFilter中,new 第二步 自定义的request和response的类。并把它们分别传递 到 过滤器链
4.把该filter配置到 过滤器链的第一个位置上

/** 这个类是spring-session的1.30源码,也是实现上面第一到第三步的关键类 **/
public class SessionRepositoryFilter<S extends ExpiringSession>
    extends OncePerRequestFilter {

  /** session存储容器接口,redis、mongoDB、genfire等数据库都是实现该接口 **/
  private final SessionRepository<S> sessionRepository;

  private ServletContext servletContext;
  /**
   sessionID的传递方式接口。目前spring-session自带两个实现类
   1.cookie方式 :CookieHttpSessionStrategy
   2.http header 方式:HeaderHttpSessionStrategy
   当然,我们也可以自定义其他方式。
  **/
  private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();

  public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
    if (sessionRepository == null) {
      throw new IllegalArgumentException("sessionRepository cannot be null");
    }
    this.sessionRepository = sessionRepository;
  }

  public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
    if (httpSessionStrategy == null) {
      throw new IllegalArgumentException("httpSessionStrategy cannot be null");
    }
    /**
    通过前面的spring-session功能介绍,我们知道spring-session可以支持单浏览器多
    session, 就是通过MultiHttpSessionStrategyAdapter来实现的。
    每个浏览器拥有一个sessionID,但是这个sessionID拥有多个别名(根据浏览器的tab)。如:
        别名1 sessionID
        别名2 sessionID
        ...
        而这个别名通过url来传递,这就是单浏览器多session原理了
        **/
    this.httpSessionStrategy = new MultiHttpSessionStrategyAdapter(
        httpSessionStrategy);
  }

  public void setHttpSessionStrategy(MultiHttpSessionStrategy httpSessionStrategy) {
    if (httpSessionStrategy == null) {
      throw new IllegalArgumentException("httpSessionStrategy cannot be null");
    }
    this.httpSessionStrategy = httpSessionStrategy;
  }
   /**
  该方法相当于重写了doFilter,只是spring-session又做了多一层封装。
  在这个方法里创建自定义的 request和response,然后传递到过滤器链filterChain
   **/
  @Override
  protected void doFilterInternal(HttpServletRequest request,
      HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        /**
        spring-session重写的ServletRequest。这个类继承了HttpServletRequestWrapper
        **/
    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
        request, response, this.servletContext);
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
        wrappedRequest, response);

    HttpServletRequest strategyRequest = this.httpSessionStrategy
        .wrapRequest(wrappedRequest, wrappedResponse);
    HttpServletResponse strategyResponse = this.httpSessionStrategy
        .wrapResponse(wrappedRequest, wrappedResponse);

    try {
       /**
       传递自定义 request和response到链中,想象下如果
       该spring-sessionFilter位于过滤器链的第一个,那么后续的Filter,
       以及到达最后的控制层所获取的 request和response,是不是就是我们自定义的了?
       **/
      filterChain.doFilter(strategyRequest, strategyResponse);
    }
    finally {
      wrappedRequest.commitSession();
    }
  }

  public void setServletContext(ServletContext servletContext) {
    this.servletContext = servletContext;
  }

  /**
  这个就是Servlet response的重写类了
   */
  private final class SessionRepositoryResponseWrapper
      extends OnCommittedResponseWrapper {

    private final SessionRepositoryRequestWrapper request;

    SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request,
        HttpServletResponse response) {
      super(response);
      if (request == null) {
        throw new IllegalArgumentException("request cannot be null");
      }
      this.request = request;
    }
     /**
      这步是持久化session到存储容器,我们可能会在一个控制层里多次调用session的操作方法
      如果我们每次对session的操作都持久化到存储容器,必定会带来性能的影响。比如redis
      所以我们可以在整个控制层执行完毕了,response返回信息到浏览器时,才持久化session
     **/
    @Override
    protected void onResponseCommitted() {
      this.request.commitSession();
    }
  }

  /**
  spring-session 的request重写类,这几乎是最重要的一个重写类。里面重写了获取getSession,Session等方法以及类
   */
  private final class SessionRepositoryRequestWrapper
      extends HttpServletRequestWrapper {
    private Boolean requestedSessionIdValid;
    private boolean requestedSessionInvalidated;
    private final HttpServletResponse response;
    private final ServletContext servletContext;

    private SessionRepositoryRequestWrapper(HttpServletRequest request,
        HttpServletResponse response, ServletContext servletContext) {
      super(request);
      this.response = response;
      this.servletContext = servletContext;
    }

    /**
     * Uses the HttpSessionStrategy to write the session id to the response and
     * persist the Session.
     */
    private void commitSession() {
      HttpSessionWrapper wrappedSession = getCurrentSession();
      if (wrappedSession == null) {
          // session失效,删除cookie或者header
        if (isInvalidateClientSession()) {
          SessionRepositoryFilter.this.httpSessionStrategy
              .onInvalidateSession(this, this.response);
        }
      }
      else {
        S session = wrappedSession.getSession();
        SessionRepositoryFilter.this.sessionRepository.save(session);
        if (!isRequestedSessionIdValid()
            || !session.getId().equals(getRequestedSessionId())) {
        // 把cookie或者header写回给浏览器保存
        SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
              this, this.response);
        }
      }
    }

    @SuppressWarnings("unchecked")
    private HttpSessionWrapper getCurrentSession() {
      return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
    }

    private void setCurrentSession(HttpSessionWrapper currentSession) {
      if (currentSession == null) {
        removeAttribute(CURRENT_SESSION_ATTR);
      }
      else {
        setAttribute(CURRENT_SESSION_ATTR, currentSession);
      }
    }

    @SuppressWarnings("unused")
    public String changeSessionId() {
      HttpSession session = getSession(false);

      if (session == null) {
        throw new IllegalStateException(
            "Cannot change session ID. There is no session associated with this request.");
      }

      // eagerly get session attributes in case implementation lazily loads them
      Map<String, Object> attrs = new HashMap<String, Object>();
      Enumeration<String> iAttrNames = session.getAttributeNames();
      while (iAttrNames.hasMoreElements()) {
        String attrName = iAttrNames.nextElement();
        Object value = session.getAttribute(attrName);

        attrs.put(attrName, value);
      }

      SessionRepositoryFilter.this.sessionRepository.delete(session.getId());
      HttpSessionWrapper original = getCurrentSession();
      setCurrentSession(null);

      HttpSessionWrapper newSession = getSession();
      original.setSession(newSession.getSession());

      newSession.setMaxInactiveInterval(session.getMaxInactiveInterval());
      for (Map.Entry<String, Object> attr : attrs.entrySet()) {
        String attrName = attr.getKey();
        Object attrValue = attr.getValue();
        newSession.setAttribute(attrName, attrValue);
      }
      return newSession.getId();
    }
    // 判断session是否有效
    @Override
    public boolean isRequestedSessionIdValid() {
      if (this.requestedSessionIdValid == null) {
        String sessionId = getRequestedSessionId();
        S session = sessionId == null ? null : getSession(sessionId);
        return isRequestedSessionIdValid(session);
      }

      return this.requestedSessionIdValid;
    }

    private boolean isRequestedSessionIdValid(S session) {
      if (this.requestedSessionIdValid == null) {
        this.requestedSessionIdValid = session != null;
      }
      return this.requestedSessionIdValid;
    }

    private boolean isInvalidateClientSession() {
      return getCurrentSession() == null && this.requestedSessionInvalidated;
    }

    private S getSession(String sessionId) {
       // 从session存储容器中根据sessionID获取session
      S session = SessionRepositoryFilter.this.sessionRepository
          .getSession(sessionId);
      if (session == null) {
        return null;
      }
      // 设置sesison的最后访问时间,以防过期
      session.setLastAccessedTime(System.currentTimeMillis());
      return session;
    }
     /**
     这个方法是不是很熟悉,下面还有个getSession()才更加熟悉。没错,就是在这里重新获取session方法
     **/
    @Override
    public HttpSessionWrapper getSession(boolean create) {
      //快速获取session,可以理解为一级缓存、二级缓存这种关系
      HttpSessionWrapper currentSession = getCurrentSession();
      if (currentSession != null) {
        return currentSession;
      }
      //从httpSessionStratge里面根据cookie或者header获取sessionID
      String requestedSessionId = getRequestedSessionId();
      if (requestedSessionId != null
          && getAttribute(INVALID_SESSION_ID_ATTR) == null) {
        //从存储容器获取session以及设置当次初始化属性
        S session = getSession(requestedSessionId);
        if (session != null) {
          this.requestedSessionIdValid = true;
          currentSession = new HttpSessionWrapper(session, getServletContext());
          currentSession.setNew(false);
          setCurrentSession(currentSession);
          return currentSession;
        }
        else {

          if (SESSION_LOGGER.isDebugEnabled()) {
            SESSION_LOGGER.debug(
                "No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
          }
          setAttribute(INVALID_SESSION_ID_ATTR, "true");
        }
      }
      if (!create) {
        return null;
      }
      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)"));
      }
      // 如果该浏览器或者其他http访问者是初次访问服务器,则为他创建个新的session
      S session = SessionRepositoryFilter.this.sessionRepository.createSession();
      session.setLastAccessedTime(System.currentTimeMillis());
      currentSession = new HttpSessionWrapper(session, getServletContext());
      setCurrentSession(currentSession);
      return currentSession;
    }

    @Override
    public ServletContext getServletContext() {
      if (this.servletContext != null) {
        return this.servletContext;
      }
      // Servlet 3.0+
      return super.getServletContext();
    }

    @Override
    public HttpSessionWrapper getSession() {
      return getSession(true);
    }

    @Override
    public String getRequestedSessionId() {
      return SessionRepositoryFilter.this.httpSessionStrategy
          .getRequestedSessionId(this);
    }

    /**
    HttpSession的重写类
     */
    private final class HttpSessionWrapper extends ExpiringSessionHttpSession<S> {

      HttpSessionWrapper(S session, ServletContext servletContext) {
        super(session, servletContext);
      }

      @Override
      public void invalidate() {
        super.invalidate();
        SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
        setCurrentSession(null);
        SessionRepositoryFilter.this.sessionRepository.delete(getId());
      }
    }
  }
}

总结

以上就是本文关于spring-session简介及实现原理源码分析的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出!

(0)

相关推荐

  • Spring Boot项目利用Redis实现session管理实例

    在现代网络服务中,session(会话)不得不说是非常重要也是一定要实现的概念,因此在web后台开发中,对session的管理和维护是必须要实现的组件.这篇文章主要是介绍如何在Spring Boot项目中加入redis来实现对session的存储与管理. 1. 利用Spring Initializr来新建一个spring boot项目 2. 在pom.xml中添加redis和session的相关依赖.项目生成的时候虽然也会自动生成父依赖,但是1.5.3版本的spring boot的redis相关

  • 详解SpringBoot中Session超时原理说明

    一:前言: 最近支付后台登录一段时间后如果没有任何操作,总是需要重新登录才可以继续访问页面,出现这个问题的原因就是session超时,debug代码后发现session的超时时间是1800s.也就是说当1800秒内没有任何操作,session就会出现超时现象.那这个超时时间是如何设置的呢?然后该如何重新设置此超时时间呢?系统又如何判断session超时的呢?接下来就一一进行解答. 二:系统session超时时间如何默认的? 说明:获取session超时时间的方法为"request.getSess

  • Spring Boot报错:No session repository could be auto-configured, check your configuration的解决方法

    本文主要跟大家分享了关于Spring Boot报错:No session repository could be auto-configured, check your configuration的解决方法,下面话不多说,来一起看看详细的介绍: 一.环境介绍 JDK 1.8  spring-Boot 1.5.1.RELEASE, STS IDE 二. 问题的提出 创建了一个非常简约的Spring Boot Web Application,其中使用了Spring-Session,具体的maven依

  • 详解Spring-Boot集成Spring session并存入redis

    spring Session 提供了一套用于管理用户 session 信息的API和实现. Spring Session为企业级Java应用的session管理带来了革新,使得以下的功能更加容易实现: 编写可水平扩展的原生云应用. 将session所保存的状态卸载到特定的外部session存储中,如Redis或Apache Geode中,它们能够以独立于应用服务器的方式提供高质量的集群. 当用户使用WebSocket发送请求的时候,能够保持HttpSession处于活跃状态. 在非Web请求的处

  • 利用Spring Session和redis对Session进行共享详解

    前言 我们在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理. session的处理有很多种方法,详情见转载的上篇博客:集群/分布式环境下5种session处理策略 在这里我们讨论其中的第三种方法:session共享. redis集群做主从复制,利用redis数据库的最终一致性,将session信息存入redis中.当应用服务器发现session不在本机内存的时候,就去redis数据库中查找,因为redis数据库是独立于应用服务器的数据库,所以可以做到session

  • 详解基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案

    分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多:还有一种是利用一些tomcat上的插件,修改tomcat配置文件,让tomcat自己去把Session放到Redis/Memcached/DB中去.这两种各有优缺,也都能解决问题. 但是现在项目全线Spring Boot,并不自己维护Tomcat,而是由Spring去启动Tomcat.这样就会有一

  • Spring MVC 中获取session的几种方法(小结)

    Spring MVC 中使用session是一种常见的操作,但是大家上网搜索一下可以看到获取session的方式方法五花八门,最近,自己终结了一下,将获取session的方法记录下来,以便大家共同学习进步. 第一种:将HttpSession作为Spring MVC 的方法参数传入,直接获取. 直接在Spring MVC 的方法中将参数传入: public void getSessionAction(HttpSession session){ } 这种方法我再网上搜索时发现很多人并不推荐使用,但是

  • Spring Session实现分布式session的简单示例

    前面有用 tomcat-redis-session-manager来实现分布式session管理,但是它有一定的局限性,主要是跟tomcat绑定太紧了,这里改成用Spring Session来管理分布式session,Spring Session就完全实现了与具体的容器无关,如果需要了解如何用tomcat-redis-session-manager实现分分布式session,请看我之前的文章,下面正式进入主题,Spring Session项目搭建. 1. 引入Spring Session mav

  • spring-session简介及实现原理源码分析

    一:spring-session介绍 1.简介 session一直都是我们做集群时需要解决的一个难题,过去我们可以从serlvet容器上解决,比如开源servlet容器-tomcat提供的tomcat-redis-session-manager.memcached-session-manager. 或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上.. 但是这两种办法都存在弊端. spring-session是spring旗下的一个项目,把servlet容器实现的httpSe

  • Spring的Model 和 Map的原理源码解析

    Model 和 Map 为什么在Model和Map中放值传入后会出现在request的上面. 9.1.源码解析 准备测试代码 @GetMapping("/goto") public String go(HttpServletRequest request, Map<String,Object> map, Model model){ request.setAttribute("msg","传过来...."); map.put("

  • MyBatis框架底层的执行原理源码解析

    目录 1.前言 2.案例项目源码 3.MyBatis源码解析底层执行原理 3.1 读取mybatis配置文件创建出SqlSeesionFactory对象 3.2 通过SqlSeesionFactory对象进而创建出SqlSession对象 3.3 通过SqlSession的getMapper获取到接口代理对象 3.4 通过mapper接口的代理对象执行CRUD 1.前言 MyBatis框架大家肯定都用过的,废话我就不再多说了,这篇文章就给大家分享一下有关MyBatis框架底层的执行原理吧(Deb

  • python装饰器原理源码示例分析

    目录 前言 一.什么是装饰器 二.为什么要用装饰器 三.简单的装饰器 四.装饰器的语法糖 五.装饰器传参 六.带参数的装饰器 七.类装饰器 八.带参数的类装饰器 九.装饰器的顺序 前言 最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡.你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡.但是你喜欢的女孩子还是你喜欢的女孩子.如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该

  • Vue解读之响应式原理源码剖析

    目录 初始化 initState() initProps() initData() observe() Observer defineReactive() 依赖收集 Dep Watcher 依赖收集过程 移除订阅 派发更新 notify() update() queueWatcher() flushSchedulerQueue() updated() defineProperty 缺陷及处理 Vue.set() 重写数组方法 总结 先看张图,了解一下大体流程和要做的事 初始化 在 new Vue

  • Vue watch原理源码层深入讲解

    目录 添加依赖 触发依赖 总结 由于我在从源码看vue(v2.7.10)的computed的实现原理中详细的讲解过computed的实现,本篇跟computed的原理类似.我就带大家简单分析一下. 添加依赖 代码如下: <template> <div> {{a}} <button @click="addModule">新增</button> </div> </template> <script> exp

  • React Hydrate原理源码解析

    目录 引言 Demo ReactDOM.render ReactDOM.hydrate hydrate 过程 事件绑定 hydrate 源码剖析 beginWork HostRoot Fiber HostComponent HostText Fiber tryToClaimNextHydratableInstance completeUnitOfWork popHydrationState prepareToHydrateHostInstance prepareToHydrateHostText

  • go slice 扩容实现原理源码解析

    目录 正文 扩容的示例 实际扩容倍数 growslice 实现 growslice 实现步骤 growslice 源码剖析 总结 正文 基于 Go 1.19. go 的切片我们都知道可以自动地进行扩容,具体来说就是在切片的容量容纳不下新的元素的时候, 底层会帮我们为切片的底层数组分配更大的内存空间,然后把旧的切片的底层数组指针指向新的内存中: 目前网上一些关于扩容倍数的文章都是基于相对旧版本的 Go 的,新版本中,现在切片扩容的时候并不是那种准确的小于多少容量的时候就 2 倍扩容, 大于多少容量

  • axios拦截器工作方式及原理源码解析

    目录 axios 拦截器的配置方式 use() 方法的定义 拦截器如何执行 拦截器回调方法的添加顺序 同步执行请求拦截器(顺序执行) 异步执行请求拦截器(同时执行) Q&A 拦截器是如何工作的 拦截器的执行顺序 同步&异步 axios 拦截器的配置方式 本文所用 axios 版本号为:1.3.2. axios 中有两种拦截器: axios.interceptors.request.use(onFulfilled, onRejected, options):配置请求拦截器. onFulfil

  • Flink时间和窗口逻辑处理源码分析

    目录 概览 时间 重要类 WatermarkStrategy WatermarkGenerator TimerService 处理逻辑 窗口 重要类 Window WindowAssigner Triger Evictor WindowOperator InternalAppendingState 处理逻辑 总结 概览 计算模型 DataStream基础框架 事件时间和窗口 部署&调度 存储体系 底层支撑 在实时计算处理时,需要跟时间来打交道,如实时风控场景的时间行为序列,实时分析场景下的时间窗

随机推荐