對於有登陸校驗的網站,tomcat 重啓以後,刷新頁面又得從新登陸,影響用戶體驗.java
緣由:
tomcat 的session 在內存中,tomcat重啓以後,內存中的session就銷燬了.致使登陸信息丟失git
對於存儲在session中的信息,服務器重啓以後,不會丟失. 好比用戶登陸以後,重啓tomcat服務器,刷新頁面,依然是登陸狀態.github
getAttribute(String s)
, setAttribute(String s, Object o)
, removeAttribute(String s)
;在RequestbodyFilter 中使用責任鏈設計模式編寫一套自定義的請求過濾器
主要類以下:
鏈條:redis
public class RequestFilterChain { private List<IRequestFilter> filterList = new ArrayList<>(); private int index = 0; private boolean hasAddDefaultFilter = false; public RequestFilterChain addFilter(IRequestFilter filter) { if (hasAddDefaultFilter) { throw new RuntimeException("自定義過濾器必須在默認過濾器以前添加"); } this.filterList.add(filter); return this; } public RequestFilterChain addDefaultFilter(IRequestFilter filter) { this.filterList.add(filter); hasAddDefaultFilter = true; // DefaultFormRequestWrapperFilter defaultDaoFilter = (DefaultFormRequestWrapperFilter) filter; return this; } public void reset() { this.index = 0; } private IRequestFilter next() { if (index == filterList.size()) { return null; } IRequestFilter filter = filterList.get(index); index++; return filter; } public void doFilter(HttpPutFormContentRequestWrapper request, HttpServletResponse response) throws IOException, ServletException { IRequestFilter filter = next(); if (null == filter) { System.out.println("結束 index :" + index); return; } filter.doFilter(request, response, this); } }
請求處理接口 :設計模式
public interface IRequestFilter { void doFilter(HttpPutFormContentRequestWrapper request, HttpServletResponse response, RequestFilterChain filterChain) throws IOException, ServletException; }
在RequestbodyFilter 中處理鏈條:瀏覽器
wrapper.setChain(chain); RequestFilterChain requestFilterChain = new RequestFilterChain(); addRequestFilter(requestFilterChain); HttpServletRequest httpServletRequest1 = (HttpServletRequest) request; boolean isLocalIp = WebServletUtil.isLocalIp(httpServletRequest1); if (!isLocalIp) { requestFilterChain.addFilter(new DecideUseCacheWhenOvertimeFilter()); } requestFilterChain.addDefaultFilter(new DefaultFormRequestWrapperFilter()); requestFilterChain.doFilter(wrapper, (HttpServletResponse) response); wrapper.resetCustom();
HttpSessionSyncShareFilter 中作了兩件事:tomcat
HttpSessionSyncShareFilter中核心方法:服務器
@Override public void doFilter(HttpPutFormContentRequestWrapper request, HttpServletResponse response, RequestFilterChain filterChain) throws IOException, ServletException { ISharableSessionAPI sharedSessionAPI = getSharableSessionAPI(request, this.sessionImplType); System.out.println("sharableSessionAPI :" + sharedSessionAPI.getClass().getSimpleName()); SessionSyncRequestWrapper sessionSyncRequestWrapper = new SessionSyncRequestWrapper(request, sharedSessionAPI); if (null == sessionSyncRequestWrapper.getChain()) {//NOTICE:必定要有這個判斷 sessionSyncRequestWrapper.setChain(request.getChain()); } filterChain.doFilter(sessionSyncRequestWrapper, response); }
在SessionSyncRequestWrapper 中重寫 getSession(boolean create)
,這樣在業務代碼中調用getSession(boolean create)方法時,
獲取的是咱們自定義的session處理類 CustomSharedHttpSession,
CustomSharedHttpSession 繼承 javax.servlet.http.HttpSessioncookie
/*** * 須要重寫 * @param s * @return */ @Override public Object getAttribute(String s) { Object o1 = null; if (null == this.getHttpSession()) { o1 = sharedSessionAPI.getSessionAttribute(this.JSESSIONID, s); return o1; } Object o = this.httpSession.getAttribute(s); if (o == null) { String currentSessionId = this.httpSession.getId(); o = sharedSessionAPI.getSessionAttribute(currentSessionId, s); if (null != o) { //使用新的 JSESSIONID 保存到redis 中 this.setAttribute(s, o); return o; } if ((!currentSessionId.equals(this.JSESSIONID))) { Object o2 = sharedSessionAPI.getSessionAttribute(this.JSESSIONID, s); //RedisCacheUtil.getSessionAttribute(this.JSESSIONID + s); if (null != o2) { this.httpSession.setAttribute(s, o2); //此時 this.JSESSIONID有值,可是currentSessionId沒有值,全部要手動同步 sharedSessionAPI.setSessionAttribute(currentSessionId, s, o2); o = o2; } } } return o; }
/** * 須要重寫 * * @param s * @param o */ @Override public void setAttribute(String s, Object o) { String sessionId = null; if (null == this.httpSession) { sessionId = this.JSESSIONID; } else { this.httpSession.setAttribute(s, o); sessionId = this.httpSession.getId(); } sharedSessionAPI.setSessionAttribute(sessionId, s, o); }
@Override public void removeAttribute(String s) { if (null != this.httpSession) { this.httpSession.removeAttribute(s); String sessionId = this.httpSession.getId(); sharedSessionAPI.setSessionAttribute(sessionId, s, null); } sharedSessionAPI.setSessionAttribute(this.JSESSIONID, s, null); }
CustomSharedHttpSession的構造方法有三個參數:session
ISharableSessionAPI 接口 :
/*** * see CustomSharedSessionAPI,CustomSharedHttpSession */ public interface ISharableSessionAPI { Object getSessionAttribute(String sessionId, String key); void setSessionAttribute(String sessionId, String s, Object o); }
getAttribute(String s) 中的邏輯有點複雜,咱們進行詳細解析
在(1)中,嘗試獲取原始的session,
若是原始的session爲空,則調用ISharableSessionAPI獲取屬性值,直接返回,
並無判斷屬性值是否爲空.
在(2)中,若是原始session不爲空,則從原始session獲取屬性值o,
若是o不爲空則直接返回,
由於已經取到值了,沒有必要從ISharableSessionAPI 中獲取.
在(3)中,若是o爲空,就須要嘗試從ISharableSessionAPI 中獲取了;
先拿原始session的id 做爲key,來獲取,
若是屬性值不爲空,則同步到原始session中,由於剛纔在(2)中得知原始session沒有屬性值.
而後返回.
進入步驟(4)中,說明 以原始session的id 做爲key沒有獲取到值,
那麼以this.JSESSIONID 做爲key,調用ISharableSessionAPI 獲取屬性值,
若是獲取到值,則同步到原始session,
(5)中爲何又要設置一遍ISharableSessionAPI的保存呢?
這裏是關鍵!!!
這裏是關鍵!!!
這裏是關鍵!!!
緣由以下:
咱們按照時間順序走一遍流程:
瀏覽器第一次訪問服務器, 服務器生成原始session,好比key爲AAA,
登陸時,保存username到原始session 和ISharableSessionAPI 中.
此時tomcat重啓了,
第二次訪問,瀏覽器帶過去cookie ,sessionid :AAA,
可是tomcat重啓以後,原來的session屬性都沒了,因此經過AAA獲取不到屬性值,
tomcat會生成新的session id : BBB 因而以AAA爲key,調用ISharableSessionAPI,成功獲取到值,而且同步到session id : BBB,
若是沒有步驟(5)的話, (5)的做用是把屬性值同步到key爲AAA的ISharableSessionAPI中.
此時tomcat又重啓了,
第二次訪問,瀏覽器帶過去cookie ,sessionid :BBB,
tomcat重啓以後,原來的session屬性都沒了,因此經過BBB獲取不到屬性值,
tomcat會生成新的session id : CCC
因而以BBB爲key,調用ISharableSessionAPI,不可能獲取到值,
這就出現bug了,原本是有屬性值的,重啓一次,能夠獲取,重啓第二次,就沒法獲取了.