当前位置: 移动技术网 > IT编程>开发语言>Java > spring-session简介及实现原理源码分析

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

2019年07月19日  | 移动技术网IT编程  | 我要评论

一: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。

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网