原文地址:https://www.iteye.com/blog/gearever-1546423
Session管理是JavaEE容器比較重要的一部分,在app中也經(jīng)常會(huì)用到。在開(kāi)發(fā)app時(shí),我們只是獲取一個(gè)session,然后向 session中存取數(shù)據(jù),然后再銷毀session。那么如何產(chǎn)生session,以及session池如何維護(hù)及管理,這些并沒(méi)有在app涉及到。這 些工作都是由容器來(lái)完成的。? Tomcat中主要由每個(gè)context容器內(nèi)的一個(gè)Manager對(duì)象來(lái)管理session。對(duì)于這個(gè)manager對(duì)象的實(shí)現(xiàn),可以根據(jù)tomcat提供的接口或基類來(lái)自己定制,同時(shí),tomcat也提供了標(biāo)準(zhǔn)實(shí)現(xiàn)。? 在tomcat架構(gòu)分析(容器類)中已經(jīng)介紹過(guò),在每個(gè)context對(duì)象,即web app都具有一個(gè)獨(dú)立的manager對(duì)象。通過(guò)server.xml可以配置定制化的manager,也可以不配置。不管怎樣,在生成context對(duì) 象時(shí),都會(huì)生成一個(gè)manager對(duì)象。缺省的是StandardManager類,其類路徑為:
引用 org.apache.catalina.session.StandardManager
Session對(duì)象也可以定制化實(shí)現(xiàn),其主要實(shí)現(xiàn)標(biāo)準(zhǔn)servlet的session接口:
引用 javax.servlet.http.HttpSession
Tomcat也提供了標(biāo)準(zhǔn)的session實(shí)現(xiàn):
引用 org.apache.catalina.session.StandardSession
本文主要就是結(jié)合消息流程介紹這兩個(gè)類的實(shí)現(xiàn),及session機(jī)制。? Session方面牽涉的東西還是蠻多的,例如HA,session復(fù)制是其中重要部分等,不過(guò)本篇主要從功能方面介紹session管理,有時(shí)間再說(shuō)說(shuō)擴(kuò)展。? Session管理主要涉及到這幾個(gè)方面:
創(chuàng)建session 注銷session 持久化及啟動(dòng)加載session 創(chuàng)建session ? 在具體說(shuō)明session的創(chuàng)建過(guò)程之前,先看一下BS訪問(wèn)模型吧,這樣理解直觀一點(diǎn)。?
browser發(fā)送Http request; tomcat內(nèi)核Http11Processor會(huì)從HTTP request中解析出“jsessionid”(具體的解析過(guò)程為先從request的URL中解析,這是為了有的瀏覽器把cookie功能禁止后,將 URL重寫(xiě)考慮的,如果解析不出來(lái),再?gòu)腸ookie中解析相應(yīng)的jsessionid),解析完后封裝成一個(gè)request對(duì)象(當(dāng)然還有其他的 http header); servlet中獲取session,其過(guò)程是根據(jù)剛才解析得到的jsessionid(如果有的話),從session池 (session maps)中獲取相應(yīng)的session對(duì)象;這個(gè)地方有個(gè)邏輯,就是如果jsessionid為空的話(或者沒(méi)有其對(duì)應(yīng)的session對(duì)象,或者有 session對(duì)象,但此對(duì)象已經(jīng)過(guò)期超時(shí)),可以選擇創(chuàng)建一個(gè)session,或者不創(chuàng)建; 如果創(chuàng)建新session,則將session放入session池中,同時(shí)將與其相對(duì)應(yīng)的jsessionid寫(xiě)入cookie通過(guò)Http response header的方式發(fā)送給browser,然后重復(fù)第一步。 以上是session的獲取及創(chuàng)建過(guò)程。在servlet中獲取session,通常是調(diào)用request的getSession方法。這個(gè)方法 需要傳入一個(gè)boolean參數(shù),這個(gè)參數(shù)就是實(shí)現(xiàn)剛才說(shuō)的,當(dāng)jsessionid為空或從session池中獲取不到相應(yīng)的session對(duì)象時(shí),選 擇創(chuàng)建一個(gè)新的session還是不創(chuàng)建。? 看一下核心代碼邏輯;
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);?? ????????}?? ????}?? 盡管不能貼出所有代碼,但是上述的核心邏輯還是很清晰的。從中也可以看出,我們經(jīng)常在servlet中這兩種調(diào)用方式的不同;? 新創(chuàng)建session
引用 request.getSession(); 或者request.getSession(true);
不創(chuàng)建session
引用 request.getSession(false);
接下來(lái),看一下StandardManager的createSession方法,了解一下session的創(chuàng)建過(guò)程;
Java代碼 ??
public?Session?createSession(String?sessionId)?{?? 是個(gè)session數(shù)量控制邏輯,超過(guò)上限則拋異常退出?? ????if?((maxActiveSessions?>=?0)?&&?? ????????(sessions.size()?>=?maxActiveSessions))?{?? ????????rejectedSessions++;?? ????????throw?new?IllegalStateException?? ????????????(sm.getString("standardManager.createSession.ise"));?? ????}?? ????return?(super.createSession(sessionId));?? }?? 這個(gè)最大支持session數(shù)量maxActiveSessions是可以配置的,先不管這個(gè)安全控制邏輯,看其主邏輯,即調(diào)用其基類的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);?? ????}?? 關(guān)鍵是jsessionid的產(chǎn)生過(guò)程,接著看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);?? ????}?? 這里主要說(shuō)明的不是生成jsessionid的算法了,而是這個(gè)do…while結(jié)構(gòu)。把這個(gè)邏輯抽象出來(lái),可以看出;? 如圖所示,創(chuàng)建jsessionid的方式是由tomcat內(nèi)置的加密算法算出一個(gè)隨機(jī)的jsessionid,如果此jsessionid已經(jīng)存在,則重新計(jì)算一個(gè)新的,直到確保現(xiàn)在計(jì)算的jsessionid唯一。? 好了,至此一個(gè)session就這么創(chuàng)建了,像上面所說(shuō)的,返回時(shí)是將jsessionid以HTTP response的header:“Set-cookie”發(fā)給客戶端。?注銷session
Session創(chuàng)建完之后,不會(huì)一直存在,或是主動(dòng)注銷,或是超時(shí)清除。即是出于安全考慮也是為了節(jié)省內(nèi)存空間等。例如,常見(jiàn)場(chǎng)景:用戶登出系統(tǒng)時(shí),會(huì)主動(dòng)觸發(fā)注銷操作。?主動(dòng)注銷 ? 主動(dòng)注銷時(shí),是調(diào)用標(biāo)準(zhǔn)的servlet接口:
引用 session.invalidate();
看一下tomcat提供的標(biāo)準(zhǔn)session實(shí)現(xiàn)(StandardSession)
Java代碼 ??
public?void?invalidate()?{?? ????????if?(!isValidInternal())?? ????????????throw?new?IllegalStateException?? ????????????????(sm.getString("standardSession.invalidate.ise"));?? ???????? ????????expire();?? ????}?? Expire方法的邏輯稍后再說(shuō),先看看超時(shí)注銷,因?yàn)樗鼈冋{(diào)用的是同一個(gè)expire方法。?超時(shí)注銷 ? Tomcat定義了一個(gè)最大空閑超時(shí)時(shí)間,也就是說(shuō)當(dāng)session沒(méi)有被操作超過(guò)這個(gè)最大空閑時(shí)間時(shí)間時(shí),再次操作這個(gè)session,這個(gè)session就會(huì)觸發(fā)expire。? 這個(gè)方法封裝在StandardSession中的isValid()方法內(nèi),這個(gè)方法在獲取這個(gè)request請(qǐng)求對(duì)應(yīng)的session對(duì)象時(shí) 調(diào)用,可以參看上面說(shuō)的創(chuàng)建session環(huán)節(jié)。也就是說(shuō),獲取session的邏輯是,先從manager控制的session池中獲取對(duì)應(yīng) jsessionid的session對(duì)象,如果獲取到,就再判斷是否超時(shí),如果超時(shí),就expire這個(gè)session了。? 看一下tomcat提供的標(biāo)準(zhǔn)session實(shí)現(xiàn)(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方法 ? 是時(shí)候來(lái)看看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);?? ????????????......?? ????????}?? ????}?? 不需要解釋,已經(jīng)很清晰了。? 這個(gè)超時(shí)時(shí)間是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各個(gè)app下的web.xml自行定義;
Xml代碼 ??
<session-config>?? ????<session-timeout>30</session-timeout>?? </session-config>?? 單位是分鐘。?Session持久化及啟動(dòng)初始化 ? 這個(gè)功能主要是,當(dāng)tomcat執(zhí)行安全退出時(shí)(通過(guò)執(zhí)行shutdown腳本),會(huì)將session持久化到本地文件,通常在tomcat的部 署目錄下有個(gè)session.ser文件。當(dāng)啟動(dòng)tomcat時(shí),會(huì)從這個(gè)文件讀入session,并添加到manager的session池中去。? 這樣,當(dāng)tomcat正常重啟時(shí), session沒(méi)有丟失,對(duì)于用戶而言,體會(huì)不到重啟,不影響用戶體驗(yàn)。? 看一下概念圖吧,覺(jué)得不是重要實(shí)現(xiàn)邏輯,代碼就不說(shuō)了。?
Session持久化可以實(shí)現(xiàn)當(dāng)tomcat重新啟動(dòng)后,當(dāng)前IE使用的session仍然有效而不用重新登錄,有兩步需要做,session持久 化很有用,尤其在eclipse中重新增加類后,tomcat重新加載后,IE頁(yè)面不用再登錄,之前的session依舊保持,調(diào)試的時(shí)候很有用 1.配置conf/server.xml 在server.xml的根路徑或虛擬目錄中增加一段,如虛擬目錄調(diào)度所中: <Context path="/dds" docBase="D:/01_XZY/98_供電局調(diào)度所/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對(duì)象(像字符串類的)就能實(shí)現(xiàn)持久化了,但如果session使用了對(duì)象,則必須實(shí)現(xiàn)對(duì)象類的可序列化 參數(shù)說(shuō)明:saveOnRestart-服務(wù)器關(guān)閉時(shí),是否將所有的session保存到文件中; maxActiveSessions-可處于活動(dòng)狀態(tài)的session數(shù); minIdleSwap/maxIdleSwap-session處于不活動(dòng)狀態(tài)最短/長(zhǎng)時(shí)間(s),sesson對(duì)象轉(zhuǎn)移到File Store中; maxIdleBackup-超過(guò)這一時(shí)間,將session備份。(-1表示沒(méi)有限制) directory="seskep"? 會(huì)在tomcat的work目錄下建立一個(gè)目錄,用來(lái)存儲(chǔ)session,這里建立的目錄是 D:/JAVA/TOMCAT4/work/Standalone/localhost/dds/seskep
2.類的序列化 如果session中存儲(chǔ)了類xzy登錄屬性的實(shí)例,則類xzy登錄屬性必須能夠序列化,才能實(shí)現(xiàn)session持久化 實(shí)現(xiàn)implements java.io.Serializable接口就可以了 public class xzy登錄屬性 implements java.io.Serializable? { ? public String UserName=null; ? public String 姓名=null; ? public String 單位=null; ? public String 部門(mén)=null; ? public String 職務(wù)=null; ? public String 權(quán)限設(shè)置=null; ? static final public long serialVersionUID=372938; } serialVersionUID這個(gè)東西估計(jì)每個(gè)類中寫(xiě)個(gè)不同的數(shù)值就可以了,好像是版本保持的. 經(jīng)過(guò)測(cè)試IE登錄后頁(yè)面后,Session再重新啟動(dòng),刷新IE頁(yè)面的時(shí)候session對(duì)象中的值能夠像重啟前一樣的讀出來(lái).
總結(jié) ? 由此可以看出,session的管理是容器層做的事情,應(yīng)用層一般不會(huì)參與session的管理,也就是說(shuō),如果在應(yīng)用層獲取到相應(yīng)的 session,已經(jīng)是由tomcat提供的,因此如果過(guò)多的依賴session機(jī)制來(lái)進(jìn)行一些操作,例如訪問(wèn)控制,安全登錄等就不是十分的安全,因?yàn)槿?果有人能得到正在使用的jsessionid,則就可以侵入系統(tǒng)。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/11452811.html
總結(jié)
以上是生活随笔 為你收集整理的tomcat架构分析 (Session管理)【转】 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。