原文地址:https://www.iteye.com/blog/gearever-1546423
Session管理是JavaEE容器比較重要的一部分,在app中也經常會用到。在開發app時,我們只是獲取一個session,然后向 session中存取數據,然后再銷毀session。那么如何產生session,以及session池如何維護及管理,這些并沒有在app涉及到。這 些工作都是由容器來完成的。? Tomcat中主要由每個context容器內的一個Manager對象來管理session。對于這個manager對象的實現,可以根據tomcat提供的接口或基類來自己定制,同時,tomcat也提供了標準實現。? 在tomcat架構分析(容器類)中已經介紹過,在每個context對象,即web app都具有一個獨立的manager對象。通過server.xml可以配置定制化的manager,也可以不配置。不管怎樣,在生成context對 象時,都會生成一個manager對象。缺省的是StandardManager類,其類路徑為:
引用 org.apache.catalina.session.StandardManager
Session對象也可以定制化實現,其主要實現標準servlet的session接口:
引用 javax.servlet.http.HttpSession
Tomcat也提供了標準的session實現:
引用 org.apache.catalina.session.StandardSession
本文主要就是結合消息流程介紹這兩個類的實現,及session機制。? Session方面牽涉的東西還是蠻多的,例如HA,session復制是其中重要部分等,不過本篇主要從功能方面介紹session管理,有時間再說說擴展。? Session管理主要涉及到這幾個方面:
創建session 注銷session 持久化及啟動加載session 創建session ? 在具體說明session的創建過程之前,先看一下BS訪問模型吧,這樣理解直觀一點。?
browser發送Http request; tomcat內核Http11Processor會從HTTP request中解析出“jsessionid”(具體的解析過程為先從request的URL中解析,這是為了有的瀏覽器把cookie功能禁止后,將 URL重寫考慮的,如果解析不出來,再從cookie中解析相應的jsessionid),解析完后封裝成一個request對象(當然還有其他的 http header); servlet中獲取session,其過程是根據剛才解析得到的jsessionid(如果有的話),從session池 (session maps)中獲取相應的session對象;這個地方有個邏輯,就是如果jsessionid為空的話(或者沒有其對應的session對象,或者有 session對象,但此對象已經過期超時),可以選擇創建一個session,或者不創建; 如果創建新session,則將session放入session池中,同時將與其相對應的jsessionid寫入cookie通過Http response header的方式發送給browser,然后重復第一步。 以上是session的獲取及創建過程。在servlet中獲取session,通常是調用request的getSession方法。這個方法 需要傳入一個boolean參數,這個參數就是實現剛才說的,當jsessionid為空或從session池中獲取不到相應的session對象時,選 擇創建一個新的session還是不創建。? 看一下核心代碼邏輯;
Java代碼 ??
protected?Session?doGetSession(boolean?create)?{?? ?? ????????……?? ???????? ????????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.getCookies()?&&?? ????????????response.getResponse().isCommitted())?{?? ????????????throw?new?IllegalStateException?? ??????????????(sm.getString("coyoteRequest.sessionCreateCommitted"));?? ????????}?? ?? ???????? ????????if?(connector.getEmptySessionPath()??? ????????????????&&?isRequestedSessionIdFromCookie())?{?? ????????????session?=?manager.createSession(getRequestedSessionId());?? ????????}?else?{?? ????????????session?=?manager.createSession(null);?? ????????}?? ?? ???????? ????????if?((session?!=?null)?&&?(getContext()?!=?null)?? ???????????????&&?getContext().getCookies())?{?? ????????????Cookie?cookie?=?new?Cookie(Globals.SESSION_COOKIE_NAME,?? ???????????????????????????????????????session.getIdInternal());?? ????????????configureSessionCookie(cookie);?? ????????????response.addCookieInternal(cookie);?? ????????}?? ???????? ????????if?(session?!=?null)?{?? ????????????session.access();?? ????????????return?(session);?? ????????}?else?{?? ????????????return?(null);?? ????????}?? ????}?? 盡管不能貼出所有代碼,但是上述的核心邏輯還是很清晰的。從中也可以看出,我們經常在servlet中這兩種調用方式的不同;? 新創建session
引用 request.getSession(); 或者request.getSession(true);
不創建session
引用 request.getSession(false);
接下來,看一下StandardManager的createSession方法,了解一下session的創建過程;
Java代碼 ??
public?Session?createSession(String?sessionId)?{?? 是個session數量控制邏輯,超過上限則拋異常退出?? ????if?((maxActiveSessions?>=?0)?&&?? ????????(sessions.size()?>=?maxActiveSessions))?{?? ????????rejectedSessions++;?? ????????throw?new?IllegalStateException?? ????????????(sm.getString("standardManager.createSession.ise"));?? ????}?? ????return?(super.createSession(sessionId));?? }?? 這個最大支持session數量maxActiveSessions是可以配置的,先不管這個安全控制邏輯,看其主邏輯,即調用其基類的createSession方法;
Java代碼 ??
public?Session?createSession(String?sessionId)?{?? ?????????? ???????? ????????Session?session?=?createEmptySession();?? ?? ???????? ????????session.setNew(true);?? ????????session.setValid(true);?? ????????session.setCreationTime(System.currentTimeMillis());?? ????????session.setMaxInactiveInterval(this.maxInactiveInterval);?? ????????if?(sessionId?==?null)?{?? ???????????? ????????????sessionId?=?generateSessionId();?? ????????}?? ????????session.setId(sessionId);?? ????????sessionCounter++;?? ????????return?(session);?? ????}?? 關鍵是jsessionid的產生過程,接著看generateSessionId方法;
Java代碼 ??
protected?synchronized?String?generateSessionId()?{?? ?? ????????byte?random[]?=?new?byte[16];?? ????????String?jvmRoute?=?getJvmRoute();?? ????????String?result?=?null;?? ?? ???????? ????????StringBuffer?buffer?=?new?StringBuffer();?? ????????do?{?? ????????????int?resultLenBytes?=?0;?? ????????????if?(result?!=?null)?{?? ????????????????buffer?=?new?StringBuffer();?? ????????????????duplicates++;?? ????????????}?? ?? ????????????while?(resultLenBytes?<?this.sessionIdLength)?{?? ????????????????getRandomBytes(random);?? ????????????????random?=?getDigest().digest(random);?? ????????????????for?(int?j?=?0;?? ????????????????j?<?random.length?&&?resultLenBytes?<?this.sessionIdLength;?? ????????????????j++)?{?? ????????????????????byte?b1?=?(byte)?((random[j]?&?0xf0)?>>?4);?? ????????????????????byte?b2?=?(byte)?(random[j]?&?0x0f);?? ????????????????????if?(b1?<?10)?? ????????????????????????buffer.append((char)?('0'?+?b1));?? ????????????????????else?? ????????????????????????buffer.append((char)?('A'?+?(b1?-?10)));?? ????????????????????if?(b2?<?10)?? ????????????????????????buffer.append((char)?('0'?+?b2));?? ????????????????????else?? ????????????????????????buffer.append((char)?('A'?+?(b2?-?10)));?? ????????????????????resultLenBytes++;?? ????????????????}?? ????????????}?? ????????????if?(jvmRoute?!=?null)?{?? ????????????????buffer.append('.').append(jvmRoute);?? ????????????}?? ????????????result?=?buffer.toString();?? ???????? ????????}?while?(sessions.containsKey(result));?? ????????return?(result);?? ????}?? 這里主要說明的不是生成jsessionid的算法了,而是這個do…while結構。把這個邏輯抽象出來,可以看出;? 如圖所示,創建jsessionid的方式是由tomcat內置的加密算法算出一個隨機的jsessionid,如果此jsessionid已經存在,則重新計算一個新的,直到確保現在計算的jsessionid唯一。? 好了,至此一個session就這么創建了,像上面所說的,返回時是將jsessionid以HTTP response的header:“Set-cookie”發給客戶端。?注銷session
Session創建完之后,不會一直存在,或是主動注銷,或是超時清除。即是出于安全考慮也是為了節省內存空間等。例如,常見場景:用戶登出系統時,會主動觸發注銷操作。?主動注銷 ? 主動注銷時,是調用標準的servlet接口:
引用 session.invalidate();
看一下tomcat提供的標準session實現(StandardSession)
Java代碼 ??
public?void?invalidate()?{?? ????????if?(!isValidInternal())?? ????????????throw?new?IllegalStateException?? ????????????????(sm.getString("standardSession.invalidate.ise"));?? ???????? ????????expire();?? ????}?? Expire方法的邏輯稍后再說,先看看超時注銷,因為它們調用的是同一個expire方法。?超時注銷 ? Tomcat定義了一個最大空閑超時時間,也就是說當session沒有被操作超過這個最大空閑時間時間時,再次操作這個session,這個session就會觸發expire。? 這個方法封裝在StandardSession中的isValid()方法內,這個方法在獲取這個request請求對應的session對象時 調用,可以參看上面說的創建session環節。也就是說,獲取session的邏輯是,先從manager控制的session池中獲取對應 jsessionid的session對象,如果獲取到,就再判斷是否超時,如果超時,就expire這個session了。? 看一下tomcat提供的標準session實現(StandardSession)
Java代碼 ??
public?boolean?isValid()?{?? ????????……?? ???????? ????????if?(maxInactiveInterval?>=?0)?{??? ????????????long?timeNow?=?System.currentTimeMillis();?? ????????????int?timeIdle?=?(int)?((timeNow?-?thisAccessedTime)?/?1000L);?? ????????????if?(timeIdle?>=?maxInactiveInterval)?{?? ????????????????expire(true);?? ????????????}?? ????????}?? ????????return?(this.isValid);?? ????}?? Expire方法 ? 是時候來看看expire方法了。
Java代碼 ??
public?void?expire(boolean?notify)?{??? ?? ????????synchronized?(this)?{?? ????????????......?? ???????????? ????????????setValid(false);?? ?? ???????????? ????????????long?timeNow?=?System.currentTimeMillis();?? ????????????int?timeAlive?=?(int)?((timeNow?-?creationTime)/1000);?? ????????????synchronized?(manager)?{?? ????????????????if?(timeAlive?>?manager.getSessionMaxAliveTime())?{?? ????????????????????manager.setSessionMaxAliveTime(timeAlive);?? ????????????????}?? ????????????????int?numExpired?=?manager.getExpiredSessions();?? ????????????????numExpired++;?? ????????????????manager.setExpiredSessions(numExpired);?? ????????????????int?average?=?manager.getSessionAverageAliveTime();?? ????????????????average?=?((average?*?(numExpired-1))?+?timeAlive)/numExpired;?? ????????????????manager.setSessionAverageAliveTime(average);?? ????????????}?? ?? ???????????? ????????????manager.remove(this);?? ????????????......?? ????????}?? ????}?? 不需要解釋,已經很清晰了。? 這個超時時間是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各個app下的web.xml自行定義;
Xml代碼 ??
<session-config>?? ????<session-timeout>30</session-timeout>?? </session-config>?? 單位是分鐘。?Session持久化及啟動初始化 ? 這個功能主要是,當tomcat執行安全退出時(通過執行shutdown腳本),會將session持久化到本地文件,通常在tomcat的部 署目錄下有個session.ser文件。當啟動tomcat時,會從這個文件讀入session,并添加到manager的session池中去。? 這樣,當tomcat正常重啟時, session沒有丟失,對于用戶而言,體會不到重啟,不影響用戶體驗。? 看一下概念圖吧,覺得不是重要實現邏輯,代碼就不說了。?
Session持久化可以實現當tomcat重新啟動后,當前IE使用的session仍然有效而不用重新登錄,有兩步需要做,session持久 化很有用,尤其在eclipse中重新增加類后,tomcat重新加載后,IE頁面不用再登錄,之前的session依舊保持,調試的時候很有用 1.配置conf/server.xml 在server.xml的根路徑或虛擬目錄中增加一段,如虛擬目錄調度所中: <Context path="/dds" docBase="D:/01_XZY/98_供電局調度所/02_JSP/HRDGDZC/ROOT" debug="0" reloadable="true" > <Manager className="org.apache.catalina.session.PersistentManager" debug="0" saveOnRestart="true" maxActiveSessions="-1" minIdleSwap="-1" maxIdleSwap="-1" maxIdleBackup="-1" > <Store className="org.apache.catalina.session.FileStore" directory="seskep"/> </Manager> </Context> 這樣之后,普通的session對象(像字符串類的)就能實現持久化了,但如果session使用了對象,則必須實現對象類的可序列化 參數說明:saveOnRestart-服務器關閉時,是否將所有的session保存到文件中; maxActiveSessions-可處于活動狀態的session數; minIdleSwap/maxIdleSwap-session處于不活動狀態最短/長時間(s),sesson對象轉移到File Store中; maxIdleBackup-超過這一時間,將session備份。(-1表示沒有限制) directory="seskep"? 會在tomcat的work目錄下建立一個目錄,用來存儲session,這里建立的目錄是 D:/JAVA/TOMCAT4/work/Standalone/localhost/dds/seskep
2.類的序列化 如果session中存儲了類xzy登錄屬性的實例,則類xzy登錄屬性必須能夠序列化,才能實現session持久化 實現implements java.io.Serializable接口就可以了 public class xzy登錄屬性 implements java.io.Serializable? { ? public String UserName=null; ? public String 姓名=null; ? public String 單位=null; ? public String 部門=null; ? public String 職務=null; ? public String 權限設置=null; ? static final public long serialVersionUID=372938; } serialVersionUID這個東西估計每個類中寫個不同的數值就可以了,好像是版本保持的. 經過測試IE登錄后頁面后,Session再重新啟動,刷新IE頁面的時候session對象中的值能夠像重啟前一樣的讀出來.
總結 ? 由此可以看出,session的管理是容器層做的事情,應用層一般不會參與session的管理,也就是說,如果在應用層獲取到相應的 session,已經是由tomcat提供的,因此如果過多的依賴session機制來進行一些操作,例如訪問控制,安全登錄等就不是十分的安全,因為如 果有人能得到正在使用的jsessionid,則就可以侵入系統。
轉載于:https://www.cnblogs.com/davidwang456/articles/11452811.html
總結
以上是生活随笔 為你收集整理的tomcat架构分析 (Session管理)【转】 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。