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