生活随笔
收集整理的這篇文章主要介紹了
Tomcat源码解析七:Tomcat Session管理机制
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前面幾篇我們分析了Tomcat的啟動,關閉,請求處理的流程,tomcat的classloader機制,本篇將接著分析Tomcat的session管理方面的內容。
在開始之前,我們先來看一下總體上的結構,熟悉了總體結構以后,我們在一步步的去分析源代碼。Tomcat session相光的類圖如下:
通過上圖,我們可以看出每一個StandardContext會關聯一個Manager,默認情況下Manager的實現類是StandardManager,而StandardManager內部會聚合多個Session,其中StandardSession是Session的默認實現類,當我們調用Request.getSession的時候,Tomcat通過StandardSessionFacade這個外觀類將StandardSession包裝以后返回。
上面清楚了總體的結構以后,我們來進一步的通過源代碼來分析一下。咋們首先從Request的getSession方法看起。
[java]?view plaincopy
org.apache.catalina.connector.Request#getSession??
[java]?view plaincopy
public?HttpSession?getSession()?{??????Session?session?=?doGetSession(true);??????if?(session?==?null)?{??????????return?null;??????}????????return?session.getSession();??}??
從上面的代碼,我們可以看出首先首先調用doGetSession方法獲取Session,然后再調用Session的getSession方法返回HttpSession,那接下來我們再來看看doGetSession方法:
[java]?view plaincopy
org.apache.catalina.connector.Request#doGetSession??protected?Session?doGetSession(boolean?create)?{??????????????if?(context?==?null)?{??????????return?(null);??????}??????????????if?((session?!=?null)?&&?!session.isValid())?{??????????session?=?null;??????}??????if?(session?!=?null)?{??????????return?(session);??????}????????????????????Manager?manager?=?null;??????if?(context?!=?null)?{??????????manager?=?context.getManager();??????}??????if?(manager?==?null)???????{??????????return?(null);????????????}????????????if?(requestedSessionId?!=?null)?{??????????try?{??????????????session?=?manager.findSession(requestedSessionId);??????????}?catch?(IOException?e)?{??????????????session?=?null;??????????}??????????if?((session?!=?null)?&&?!session.isValid())?{??????????????session?=?null;??????????}??????????if?(session?!=?null)?{??????????????session.access();??????????????return?(session);??????????}??????}????????????????????if?(!create)?{??????????return?(null);??????}??????if?((context?!=?null)?&&?(response?!=?null)?&&??????????context.getServletContext().getEffectiveSessionTrackingModes().??????????????????contains(SessionTrackingMode.COOKIE)?&&??????????response.getResponse().isCommitted())?{??????????throw?new?IllegalStateException????????????(sm.getString("coyoteRequest.sessionCreateCommitted"));??????}??????????????????????????????????????if?(("/".equals(context.getSessionCookiePath())??????????????&&?isRequestedSessionIdFromCookie())?||?requestedSessionSSL?)?{??????????session?=?manager.createSession(getRequestedSessionId());??????}?else?{??????????session?=?manager.createSession(null);??????}??????????????if?((session?!=?null)?&&?(getContext()?!=?null)?????????????&&?getContext().getServletContext().?????????????????????getEffectiveSessionTrackingModes().contains(?????????????????????????????SessionTrackingMode.COOKIE))?{????????????????????Cookie?cookie?=??????????????ApplicationSessionCookieConfig.createSessionCookie(??????????????????????context,?session.getIdInternal(),?isSecure());????????????response.addSessionCookieInternal(cookie);??????}????????if?(session?==?null)?{??????????return?null;??????}????????session.access();??????return?session;??}??
下面我們就來重點分析一下,上面代碼中標注了數字的地方:
標注1(第17行)首先從StandardContext中獲取對應的Manager對象,缺省情況下,這個地方獲取的其實就是StandardManager的實例。標注2(第26行)從Manager中根據requestedSessionId獲取session,如果session已經失效了,則將session置為null以便下面創建新的session,如果session不為空則通過調用session的access方法標注session的訪問時間,然后返回。標注3(第43行)判斷傳遞的參數,如果為false,則直接返回空,這其實就是對應的Request.getSession(true/false)的情況,當傳遞false的時候,如果不存在session,則直接返回空,不會新建。標注4 (第59行)調用Manager來創建一個新的session,這里默認會調用到StandardManager的方法,而StandardManager繼承了ManagerBase,那么默認其實是調用了了ManagerBase的方法。標注5 (第72行)創建了一個Cookie,而Cookie的名稱就是大家熟悉的JSESSIONID,另外JSESSIONID其實也是可以配置的,這個可以通過context節點的sessionCookieName來修改。比如….
通過doGetSession獲取到Session了以后,我們發現調用了session.getSession方法,而Session的實現類是StandardSession,那么我們再來看下StandardSession的getSession方法。
[java]?view plaincopy
org.apache.catalina.session.StandardSession#getSession??public?HttpSession?getSession()?{????????if?(facade?==?null){??????????if?(SecurityUtil.isPackageProtectionEnabled()){??????????????final?StandardSession?fsession?=?this;??????????????facade?=?AccessController.doPrivileged(??????????????????????new?PrivilegedAction<StandardSessionFacade>(){??????????????????@Override??????????????????public?StandardSessionFacade?run(){??????????????????????return?new?StandardSessionFacade(fsession);??????????????????}??????????????});??????????}?else?{??????????????facade?=?new?StandardSessionFacade(this);??????????}??????}??????return?(facade);????}??
通過上面的代碼,我們可以看到通過StandardSessionFacade的包裝類將StandardSession包裝以后返回。到這里我想大家應該熟悉了Session創建的整個流程。
接著我們再來看看,Sesssion是如何被銷毀的。我們在Tomcat啟動過程(Tomcat源代碼閱讀系列之三)中之處,在容器啟動以后會啟動一個ContainerBackgroundProcessor線程,這個線程是在Container啟動的時候啟動的,這條線程就通過后臺周期性的調用org.apache.catalina.core.ContainerBase#backgroundProcess,而backgroundProcess方法最終又會調用org.apache.catalina.session.ManagerBase#backgroundProcess,接下來我們就來看看Manger的backgroundProcess方法。
[java]?view plaincopy
org.apache.catalina.session.ManagerBase#backgroundProcess??public?void?backgroundProcess()?{??????count?=?(count?+?1)?%?processExpiresFrequency;??????if?(count?==?0)??????????processExpires();??}??
上面的代碼里,需要注意一下,默認情況下backgroundProcess是每10秒運行一次(StandardEngine構造的時候,將backgroundProcessorDelay設置為了10),而這里我們通過processExpiresFrequency來控制頻率,例如processExpiresFrequency的值默認為6,那么相當于沒一分鐘運行一次processExpires方法。接下來我們再來看看processExpires。
[java]?view plaincopy
org.apache.catalina.session.ManagerBase#processExpires??public?void?processExpires()?{????????long?timeNow?=?System.currentTimeMillis();??????Session?sessions[]?=?findSessions();??????int?expireHere?=?0?;????????if(log.isDebugEnabled())??????????log.debug("Start?expire?sessions?"?+?getName()?+?"?at?"?+?timeNow?+?"?sessioncount?"?+?sessions.length);??????for?(int?i?=?0;?i?<?sessions.length;?i++)?{??????????if?(sessions[i]!=null?&&?!sessions[i].isValid())?{??????????????expireHere++;??????????}??????}??????long?timeEnd?=?System.currentTimeMillis();??????if(log.isDebugEnabled())???????????log.debug("End?expire?sessions?"?+?getName()?+?"?processingTime?"?+?(timeEnd?-?timeNow)?+?"?expired?sessions:?"?+?expireHere);??????processingTime?+=?(?timeEnd?-?timeNow?);????}??
上面的代碼比較簡單,首先查找出當前context的所有的session,然后調用session的isValid方法,接下來我們在看看Session的isValid方法。
[java]?view plaincopy
org.apache.catalina.session.StandardSession#isValid??public?boolean?isValid()?{????????if?(this.expiring)?{??????????return?true;??????}????????if?(!this.isValid)?{??????????return?false;??????}????????if?(ACTIVITY_CHECK?&&?accessCount.get()?>?0)?{??????????return?true;??????}????????if?(maxInactiveInterval?>?0)?{??????????long?timeNow?=?System.currentTimeMillis();??????????int?timeIdle;??????????if?(LAST_ACCESS_AT_START)?{??????????????timeIdle?=?(int)?((timeNow?-?lastAccessedTime)?/?1000L);??????????}?else?{??????????????timeIdle?=?(int)?((timeNow?-?thisAccessedTime)?/?1000L);??????????}??????????if?(timeIdle?>=?maxInactiveInterval)?{??????????????expire(true);??????????}??????}????????return?(this.isValid);??}??
查看上面的代碼,主要就是通過對比當前時間和上次訪問的時間差是否大于了最大的非活動時間間隔,如果大于就會調用expire(true)方法對session進行超期處理。這里需要注意一點,默認情況下LAST_ACCESS_AT_START為false,讀者也可以通過設置系統屬性的方式進行修改,而如果采用LAST_ACCESS_AT_START的時候,那么請求本身的處理時間將不算在內。比如一個請求處理開始的時候是10:00,請求處理花了1分鐘,那么如果LAST_ACCESS_AT_START為true,則算是否超期的時候,是從10:00算起,而不是10:01。
接下來我們再來看看expire方法,代碼如下:
[java]?view plaincopy
org.apache.catalina.session.StandardSession#expire??public?void?expire(boolean?notify)?{??????????????if?(expiring?||?!isValid)??????????return;????????synchronized?(this)?{??????????????????????????????if?(expiring?||?!isValid)??????????????return;????????????if?(manager?==?null)??????????????return;????????????????????????????????expiring?=?true;????????????????????????????????Context?context?=?(Context)?manager.getContainer();??????????????????????????????????????????ClassLoader?oldTccl?=?null;??????????if?(context.getLoader()?!=?null?&&??????????????????context.getLoader().getClassLoader()?!=?null)?{??????????????oldTccl?=?Thread.currentThread().getContextClassLoader();??????????????if?(Globals.IS_SECURITY_ENABLED)?{??????????????????PrivilegedAction<Void>?pa?=?new?PrivilegedSetTccl(??????????????????????????context.getLoader().getClassLoader());??????????????????AccessController.doPrivileged(pa);??????????????}?else?{??????????????????Thread.currentThread().setContextClassLoader(??????????????????????????context.getLoader().getClassLoader());??????????????}??????????}??????????try?{????????????????????????????Object?listeners[]?=?context.getApplicationLifecycleListeners();??????????????if?(notify?&&?(listeners?!=?null))?{??????????????????HttpSessionEvent?event?=??????????????????????new?HttpSessionEvent(getSession());??????????????????for?(int?i?=?0;?i?<?listeners.length;?i++)?{??????????????????????int?j?=?(listeners.length?-?1)?-?i;??????????????????????if?(!(listeners[j]?instanceof?HttpSessionListener))??????????????????????????continue;??????????????????????HttpSessionListener?listener?=??????????????????????????(HttpSessionListener)?listeners[j];??????????????????????try?{??????????????????????????context.fireContainerEvent("beforeSessionDestroyed",??????????????????????????????????listener);??????????????????????????listener.sessionDestroyed(event);??????????????????????????context.fireContainerEvent("afterSessionDestroyed",??????????????????????????????????listener);??????????????????????}?catch?(Throwable?t)?{??????????????????????????ExceptionUtils.handleThrowable(t);??????????????????????????try?{??????????????????????????????context.fireContainerEvent(??????????????????????????????????????"afterSessionDestroyed",?listener);??????????????????????????}?catch?(Exception?e)?{????????????????????????????????????????????????????????}??????????????????????????manager.getContainer().getLogger().error??????????????????????????????(sm.getString("standardSession.sessionEvent"),?t);??????????????????????}??????????????????}??????????????}??????????}?finally?{??????????????if?(oldTccl?!=?null)?{??????????????????if?(Globals.IS_SECURITY_ENABLED)?{??????????????????????PrivilegedAction<Void>?pa?=??????????????????????????new?PrivilegedSetTccl(oldTccl);??????????????????????AccessController.doPrivileged(pa);??????????????????}?else?{??????????????????????Thread.currentThread().setContextClassLoader(oldTccl);??????????????????}??????????????}??????????}????????????if?(ACTIVITY_CHECK)?{??????????????accessCount.set(0);??????????}??????????setValid(false);????????????????????????????????manager.remove(this,?true);??????????????????????if?(notify)?{??????????????fireSessionEvent(Session.SESSION_DESTROYED_EVENT,?null);??????????}??????????????????????if?(principal?instanceof?GenericPrincipal)?{??????????????GenericPrincipal?gp?=?(GenericPrincipal)?principal;??????????????try?{??????????????????gp.logout();??????????????}?catch?(Exception?e)?{??????????????????manager.getContainer().getLogger().error(??????????????????????????sm.getString("standardSession.logoutfail"),??????????????????????????e);??????????????}??????????}??????????????????????expiring?=?false;????????????????????????????????String?keys[]?=?keys();??????????for?(int?i?=?0;?i?<?keys.length;?i++)??????????????removeAttributeInternal(keys[i],?notify);????????}????}??
上面代碼的主流程我已經標注了數字,我們來逐一分析一下:
標注1(第18行)標記當前的session為超期標注2(第41行)出發HttpSessionListener監聽器的方法。標注3(第89行)從Manager里面移除當前的session標注4(第113行)將session中保存的屬性移除。
到這里我們已經清楚了Tomcat中對與StandardSession的創建以及銷毀的過程,其實StandardSession僅僅是實現了內存中Session的存儲,而Tomcat還支持將Session持久化,以及Session集群節點間的同步。這些內容我們以后再來分析。
總結
以上是生活随笔為你收集整理的Tomcat源码解析七:Tomcat Session管理机制的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。