spring-session簡介及實(shí)現(xiàn)原理源碼分析
一:spring-session介紹
1.簡介
session一直都是我們做集群時(shí)需要解決的一個(gè)難題,過去我們可以從serlvet容器上解決,比如開源servlet容器-tomcat提供的tomcat-redis-session-manager、memcached-session-manager。
或者通過nginx之類的負(fù)載均衡做ip_hash,路由到特定的服務(wù)器上..
但是這兩種辦法都存在弊端。
spring-session是spring旗下的一個(gè)項(xiàng)目,把servlet容器實(shí)現(xiàn)的httpSession替換為spring-session,專注于解決 session管理問題。可簡單快速且無縫的集成到我們的應(yīng)用中。
2.支持功能
1)輕易把session存儲(chǔ)到第三方存儲(chǔ)容器,框架提供了redis、jvm的map、mongo、gemfire、hazelcast、jdbc等多種存儲(chǔ)session的容器的方式。
2)同一個(gè)瀏覽器同一個(gè)網(wǎng)站,支持多個(gè)session問題。
3)RestfulAPI,不依賴于cookie??赏ㄟ^header來傳遞jessionID
4)WebSocket和spring-session結(jié)合,同步生命周期管理。
3.集成方式
集成方式非常簡單,直接看官網(wǎng)的samplesandguide。http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/
主要分為以下幾個(gè)集成步驟:
1)引入依賴jar包
2)注解方式或者xml方式配置特定存儲(chǔ)容器的存儲(chǔ)方式,如redis的xml配置方式
<context:annotation-config/> /** 初始化一切spring-session準(zhǔn)備,且把springSessionFilter放入IOC **/ <beanclass="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/> /** 這是存儲(chǔ)容器的鏈接池 **/ <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框架內(nèi)部剖析
1.框架高層抽象結(jié)構(gòu)圖
2.spring-session重寫servlet request 及 redis實(shí)現(xiàn)存儲(chǔ)相關(guān)問題
spring-session無縫替換應(yīng)用服務(wù)器的request大概原理是:
1.自定義個(gè)Filter,實(shí)現(xiàn)doFilter方法
2.繼承 HttpServletRequestWrapper 、HttpServletResponseWrapper 類,重寫getSession等相關(guān)方法(在這些方法里調(diào)用相關(guān)的 session存儲(chǔ)容器操作類)。
3.在 第一步的doFilter中,new 第二步 自定義的request和response的類。并把它們分別傳遞 到 過濾器鏈
4.把該filter配置到 過濾器鏈的第一個(gè)位置上
/** 這個(gè)類是spring-session的1.30源碼,也是實(shí)現(xiàn)上面第一到第三步的關(guān)鍵類 **/ public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerRequestFilter { /** session存儲(chǔ)容器接口,redis、mongoDB、genfire等數(shù)據(jù)庫都是實(shí)現(xiàn)該接口 **/ private final SessionRepository<S> sessionRepository; private ServletContext servletContext; /** sessionID的傳遞方式接口。目前spring-session自帶兩個(gè)實(shí)現(xiàn)類 1.cookie方式 :CookieHttpSessionStrategy 2.http header 方式:HeaderHttpSessionStrategy 當(dāng)然,我們也可以自定義其他方式。 **/ 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來實(shí)現(xiàn)的。 每個(gè)瀏覽器擁有一個(gè)sessionID,但是這個(gè)sessionID擁有多個(gè)別名(根據(jù)瀏覽器的tab)。如: 別名1 sessionID 別名2 sessionID ... 而這個(gè)別名通過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; } /** 該方法相當(dāng)于重寫了doFilter,只是spring-session又做了多一層封裝。 在這個(gè)方法里創(chuàng)建自定義的 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。這個(gè)類繼承了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位于過濾器鏈的第一個(gè),那么后續(xù)的Filter, 以及到達(dá)最后的控制層所獲取的 request和response,是不是就是我們自定義的了? **/ filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } /** 這個(gè)就是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到存儲(chǔ)容器,我們可能會(huì)在一個(gè)控制層里多次調(diào)用session的操作方法 如果我們每次對session的操作都持久化到存儲(chǔ)容器,必定會(huì)帶來性能的影響。比如redis 所以我們可以在整個(gè)控制層執(zhí)行完畢了,response返回信息到瀏覽器時(shí),才持久化session **/ @Override protected void onResponseCommitted() { this.request.commitSession(); } } /** spring-session 的request重寫類,這幾乎是最重要的一個(gè)重寫類。里面重寫了獲取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或者h(yuǎn)eader 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或者h(yuǎn)eader寫回給瀏覽器保存 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存儲(chǔ)容器中根據(jù)sessionID獲取session S session = SessionRepositoryFilter.this.sessionRepository .getSession(sessionId); if (session == null) { return null; } // 設(shè)置sesison的最后訪問時(shí)間,以防過期 session.setLastAccessedTime(System.currentTimeMillis()); return session; } /** 這個(gè)方法是不是很熟悉,下面還有個(gè)getSession()才更加熟悉。沒錯(cuò),就是在這里重新獲取session方法 **/ @Override public HttpSessionWrapper getSession(boolean create) { //快速獲取session,可以理解為一級(jí)緩存、二級(jí)緩存這種關(guān)系 HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession != null) { return currentSession; } //從httpSessionStratge里面根據(jù)cookie或者h(yuǎn)eader獲取sessionID String requestedSessionId = getRequestedSessionId(); if (requestedSessionId != null && getAttribute(INVALID_SESSION_ID_ATTR) == null) { //從存儲(chǔ)容器獲取session以及設(shè)置當(dāng)次初始化屬性 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訪問者是初次訪問服務(wù)器,則為他創(chuàng)建個(gè)新的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()); } } } }
總結(jié)
以上就是本文關(guān)于spring-session簡介及實(shí)現(xiàn)原理源碼分析的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出!
- springboot中的springSession的存儲(chǔ)和獲取實(shí)現(xiàn)
- Springboot中登錄后關(guān)于cookie和session攔截問題的案例分析
- spring-redis-session 自定義 key 和過期時(shí)間
- SpringBoot2.x 整合Spring-Session實(shí)現(xiàn)Session共享功能
- Springboot實(shí)現(xiàn)多服務(wù)器session共享
- 解決前后端分離 vue+springboot 跨域 session+cookie失效問題
- 詳解SpringBoot2 使用Spring Session集群
- SpringCloud實(shí)現(xiàn)Redis在各個(gè)微服務(wù)的Session共享問題
- spring boot整合redis實(shí)現(xiàn)shiro的分布式session共享的方法
- 淺談Spring Session工作原理
相關(guān)文章
基于Java+SpringBoot+Vue前后端分離實(shí)現(xiàn)倉庫管理系統(tǒng)
這篇文章主要介紹了一個(gè)完整的倉庫管理系統(tǒng)是基于Java+Springboot + Vue前后端分離編寫的,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06使用java自帶des加密算法實(shí)現(xiàn)文件加密和字符串加密
這篇文章主要介紹了使用java自帶des加密算法實(shí)現(xiàn)文件加密和字符串加密的示例,需要的朋友可以參考下2014-03-03spring-boot項(xiàng)目啟動(dòng)遲緩異常排查解決記錄
這篇文章主要為大家介紹了spring-boot項(xiàng)目啟動(dòng)遲緩異常排查解決記錄,突然在本地啟動(dòng)不起來了,表象特征就是在本地IDEA上運(yùn)行時(shí),進(jìn)程卡住也不退出,應(yīng)用啟動(dòng)時(shí)加載相關(guān)組件的日志也不輸出2022-02-02java連接Mongodb實(shí)現(xiàn)增刪改查
這篇文章主要為大家詳細(xì)介紹了java連接Mongodb實(shí)現(xiàn)增刪改查,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03圖解Java經(jīng)典算法希爾排序的原理與實(shí)現(xiàn)
希爾排序是希爾(Donald Shell)于1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經(jīng)過改進(jìn)之后的一個(gè)更高效的版本,也稱為縮小增量排序,同時(shí)該算法是沖破O(n2)的第一批算法之一。本文會(huì)以圖解的方式詳細(xì)介紹希爾排序的基本思想及其代碼實(shí)現(xiàn)2022-09-09Mybatis實(shí)現(xiàn)單個(gè)和批量定義別名typeAliases
這篇文章主要介紹了Mybatis實(shí)現(xiàn)單個(gè)和批量定義別名typeAliases,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09