深入淺析TomCat Session管理分析
前言
對(duì)于廣大java開(kāi)發(fā)者而已,對(duì)于J2EE規(guī)范中的Session應(yīng)該并不陌生,我們可以使用Session管理用戶的會(huì)話信息,最常見(jiàn)的就是拿Session用來(lái)存放用戶登錄、身份、權(quán)限及狀態(tài)等信息。對(duì)于使用Tomcat作為Web容器的大部分開(kāi)發(fā)人員而言,Tomcat是如何實(shí)現(xiàn)Session標(biāo)記用戶和管理Session信息的呢?
概要
SESSION
Tomcat內(nèi)部定義了Session和HttpSession這兩個(gè)會(huì)話相關(guān)的接口,其類繼承體系如圖1所示。

圖1 Session類繼承體系
圖1中額外列出了Session的類繼承體系,這里對(duì)他們逐個(gè)進(jìn)行介紹。
Session:Tomcat中有關(guān)會(huì)話的基本接口規(guī)范,圖1列出了它定義的主要方法,表1對(duì)這些方法進(jìn)行介紹。
表1 Session接口說(shuō)明
| 方法 | 描述 |
| getCreationTime()/setCreationTime(time : long) | 獲取與設(shè)置Session的創(chuàng)建時(shí)間 |
| getId()/setId(id : String) | 獲取與設(shè)置Session的ID |
| getThisAccessedTime() | 獲取最近一次請(qǐng)求的開(kāi)始時(shí)間 |
| getLastAccessedTime() | 獲取最近一次請(qǐng)求的完成時(shí)間 |
| getManager()/setManager(manager : Manager) | 獲取與設(shè)置Session管理器 |
| getMaxInactiveInterval()/setMaxInactiveInterval(interval : int) | 獲取與設(shè)置Session的最大訪問(wèn)間隔 |
| getSession() | 獲取HttpSession |
| isValid()/setValid(isValid : boolean) | 獲取與設(shè)置Session的有效狀態(tài) |
| access()/endAccess() | 開(kāi)始與結(jié)束Session的訪問(wèn) |
| expire() | 設(shè)置Session過(guò)期 |
HttpSession:在HTTP客戶端與HTTP服務(wù)端提供的一種會(huì)話的接口規(guī)范,圖1列出了它定義的主要方法,表2對(duì)這些方法進(jìn)行介紹。
表2 HttpSession接口說(shuō)明
| 方法 | 描述 |
| getCreationTime() | 獲取Session的創(chuàng)建時(shí)間 |
| getId() | 獲取Session的ID |
| getLastAccessedTime() | 獲取最近一次請(qǐng)求的完成時(shí)間 |
| getServletContext() | 獲取當(dāng)前Session所屬的ServletContext |
| getMaxInactiveInterval()/setMaxInactiveInterval(interval : int) | 獲取與設(shè)置Session的最大訪問(wèn)間隔 |
| getAttribute(name : String) /setAttribute(name : String, value : Object) | 獲取與設(shè)置Session作用域的屬性 |
| removeAttribute(name : String) | 清除Session作用域的屬性 |
| invalidate() | 使Session失效并解除任何與此Session綁定的對(duì)象 |
ClusterSession:集群部署下的會(huì)話接口規(guī)范,圖1列出了它的主要方法,表3對(duì)這些方法進(jìn)行介紹。
表3 ClusterSession接口說(shuō)明
| 方法 | 描述 |
| isPrimarySession() | 是否是集群的主Session |
| setPrimarySession(boolean primarySession) | 設(shè)置集群主Session |
StandardSession:標(biāo)準(zhǔn)的HTTP Session實(shí)現(xiàn),本文將以此實(shí)現(xiàn)為例展開(kāi)。
在部署Tomcat集群時(shí),需要使集群中各個(gè)節(jié)點(diǎn)的會(huì)話狀態(tài)保持同步,目前Tomcat提供了兩種同步策略:
ReplicatedSession:每次都把整個(gè)會(huì)話對(duì)象同步給集群中的其他節(jié)點(diǎn),其他節(jié)點(diǎn)然后更新整個(gè)會(huì)話對(duì)象。這種實(shí)現(xiàn)比較簡(jiǎn)單方便,但會(huì)造成大量無(wú)效信息的傳輸。
DeltaSession:對(duì)會(huì)話中增量修改的屬性進(jìn)行同步。這種方式由于是增量的,所以會(huì)大大降低網(wǎng)絡(luò)I/O的開(kāi)銷,但是實(shí)現(xiàn)上會(huì)比較復(fù)雜因?yàn)樯婕暗綄?duì)會(huì)話屬性操作過(guò)程的管理。
SESSION管理器
Tomcat內(nèi)部定義了Manager接口用于制定Session管理器的接口規(guī)范,目前已經(jīng)有很多Session管理器的實(shí)現(xiàn),如圖2所示。

圖2 Session管理器的類繼承體系
對(duì)應(yīng)圖2中的內(nèi)容我們下面逐個(gè)描述:
Manager:Tomcat對(duì)于Session管理器定義的接口規(guī)范,圖2已經(jīng)列出了Manager接口中定義的主要方法,表4詳細(xì)描述了這些方法的作用。
表4 Manager接口說(shuō)明
| 方法 | 描述 |
| getContainer()/setContainer(container : Container) | 獲取或設(shè)置Session管理器關(guān)聯(lián)的容器,一般為Context容器 |
| getDistributable()/setDistributable(distributable : boolean) | 獲取或設(shè)置Session管理器是否支持分布式 |
| getMaxInactiveInterval()/setMaxInactiveInterval(interval : int) | 獲取或設(shè)置Session管理器創(chuàng)建的Session的最大非活動(dòng)時(shí)間間隔 |
| getSessionIdLength()/setSessionIdLength(idLength : int) | 獲取或設(shè)置Session管理器創(chuàng)建的Session ID的長(zhǎng)度 |
| getSessionCounter()/setSessionCounter(sessionCounter : long) | 獲取或設(shè)置Session管理器創(chuàng)建的Session總數(shù) |
| getMaxActive()/setMaxActive(maxActive : int) | 獲取或設(shè)置當(dāng)前已激活Session的最大數(shù)量 |
| getActiveSessions() | 獲取當(dāng)前激活的所有Session |
| getExpiredSessions()/setExpiredSessions(expiredSessions : long) | 獲取或設(shè)置當(dāng)前已過(guò)期Session的數(shù)量 |
| getRejectedSessions()/setRejectedSessions(rejectedSessions : int) | 獲取或設(shè)置已拒絕創(chuàng)建Session的數(shù)量 |
| getSessionMaxAliveTime()/setSessionMaxAliveTime(sessionMaxAliveTime : int) | 獲取或設(shè)置已過(guò)期Session中的最大活動(dòng)時(shí)長(zhǎng) |
| getSessionAverageAliveTime()/setSessionAverageAliveTime(sessionAverageAliveTime : int) | 獲取或設(shè)置已過(guò)期Session的平均活動(dòng)時(shí)長(zhǎng) |
| add(session : Session)/remove(session : Session) | 給Session管理器增加或刪除活動(dòng)Session |
| changeSessionId(session : Session) | 給Session設(shè)置新生成的隨機(jī)Session ID |
| createSession(sessionId : String) | 基于Session管理器的默認(rèn)屬性配置創(chuàng)建新的Session |
| findSession(id : String) | 返回sessionId參數(shù)唯一標(biāo)記的Session |
| findSessions() | 返回Session管理器管理的所有活動(dòng)Session |
| load()/unload() | 從持久化機(jī)制中加載Session或向持久化機(jī)制寫入Session |
| backgroundProcess() | 容器接口中定義的為具體容器在后臺(tái)處理相關(guān)工作的實(shí)現(xiàn),Session管理器基于此機(jī)制實(shí)現(xiàn)了過(guò)期Session的銷毀 |
ManagerBase:封裝了Manager接口通用實(shí)現(xiàn)的抽象類,未提供對(duì)load()/unload()等方法的實(shí)現(xiàn),需要具體子類去實(shí)現(xiàn)。所有的Session管理器都繼承自ManagerBase。
ClusterManager:在Manager接口的基礎(chǔ)上增加了集群部署下的一些接口,所有實(shí)現(xiàn)集群下Session管理的管理器都需要實(shí)現(xiàn)此接口。
PersistentManagerBase:提供了對(duì)于Session持久化的基本實(shí)現(xiàn)。
PersistentManager:繼承自PersistentManagerBase,可以在Server.xml的<Context>元素下通過(guò)配置<Store>元素來(lái)使用。PersistentManager可以將內(nèi)存中的Session信息備份到文件或數(shù)據(jù)庫(kù)中。當(dāng)備份一個(gè)Session對(duì)象時(shí),該Session對(duì)象會(huì)被復(fù)制到存儲(chǔ)器(文件或者數(shù)據(jù)庫(kù))中,而原對(duì)象仍然留在內(nèi)存中。因此即便服務(wù)器宕機(jī),仍然可以從存儲(chǔ)器中獲取活動(dòng)的Session對(duì)象。如果活動(dòng)的Session對(duì)象超過(guò)了上限值或者Session對(duì)象閑置了的時(shí)間過(guò)長(zhǎng),那么Session會(huì)被換出到存儲(chǔ)器中以節(jié)省內(nèi)存空間。
StandardManager:不用配置<Store>元素,當(dāng)Tomcat正常關(guān)閉,重啟或Web應(yīng)用重新加載時(shí),它會(huì)將內(nèi)存中的Session序列化到Tomcat目錄下的/work/Catalina/host_name/webapp_name/SESSIONS.ser文件中。當(dāng)Tomcat重啟或應(yīng)用加載完成后,Tomcat會(huì)將文件中的Session重新還原到內(nèi)存中。如果突然終止該服務(wù)器,則所有Session都將丟失,因?yàn)镾tandardManager沒(méi)有機(jī)會(huì)實(shí)現(xiàn)存盤處理。
ClusterManagerBase:提供了對(duì)于Session的集群管理實(shí)現(xiàn)。
DeltaManager:繼承自ClusterManagerBase。此Session管理器是Tomcat在集群部署下的默認(rèn)管理器,當(dāng)集群中的某一節(jié)點(diǎn)生成或修改Session后,DeltaManager將會(huì)把這些修改增量復(fù)制到其他節(jié)點(diǎn)。
BackupManager:沒(méi)有繼承ClusterManagerBase,而是直接實(shí)現(xiàn)了ClusterManager接口。是Tomcat在集群部署下的可選的Session管理器,集群中的所有Session都被全量復(fù)制到一個(gè)備份節(jié)點(diǎn)。集群中的所有節(jié)點(diǎn)都可以訪問(wèn)此備份節(jié)點(diǎn),達(dá)到Session在集群下的備份效果。
為簡(jiǎn)單起見(jiàn),本文以StandardManager為例講解Session的管理。StandardManager是StandardContext的子組件,用來(lái)管理當(dāng)前Context的所有Session的創(chuàng)建和維護(hù)。如果你應(yīng)經(jīng)閱讀或者熟悉了《Tomcat源碼分析——生命周期管理》一文的內(nèi)容,那么你就知道當(dāng)StandardContext正式啟動(dòng),也就是StandardContext的startInternal方法(見(jiàn)代碼清單1)被調(diào)用時(shí),StandardContext還會(huì)啟動(dòng)StandardManager。
代碼清單1
@Override
protected synchronized void startInternal() throws LifecycleException {
// 省略與Session管理無(wú)關(guān)的代碼
// Acquire clustered manager
Manager contextManager = null;
if (manager == null) {
if ( (getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
// 省略與Session管理無(wú)關(guān)的代碼
try {
// Start manager
if ((manager != null) && (manager instanceof Lifecycle)) {
((Lifecycle) getManager()).start();
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} catch(Exception e) {
log.error("Error manager.start()", e);
ok = false;
}
// 省略與Session管理無(wú)關(guān)的代碼
}
從代碼清單1可以看到StandardContext的startInternal方法中涉及Session管理的執(zhí)行步驟如下:
創(chuàng)建StandardManager;
如果Tomcat結(jié)合Apache做了分布式部署,會(huì)將當(dāng)前StandardManager注冊(cè)到集群中;
啟動(dòng)StandardManager;
StandardManager的start方法用于啟動(dòng)StandardManager,實(shí)現(xiàn)見(jiàn)代碼清單2。
代碼清單2
@Override
public synchronized final void start() throws LifecycleException {
//省略狀態(tài)校驗(yàn)的代碼if (state.equals(LifecycleState.NEW)) {
init();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
setState(LifecycleState.STARTING_PREP);
try {
startInternal();
} catch (LifecycleException e) {
setState(LifecycleState.FAILED);
throw e;
}
if (state.equals(LifecycleState.FAILED) ||
state.equals(LifecycleState.MUST_STOP)) {
stop();
} else {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STARTING)) {
invalidTransition(Lifecycle.AFTER_START_EVENT);
}
setState(LifecycleState.STARTED);
}
}
從代碼清單2可以看出啟動(dòng)StandardManager的步驟如下:
調(diào)用init方法初始化StandardManager;
調(diào)用startInternal方法啟動(dòng)StandardManager;
STANDARDMANAGER的初始化
經(jīng)過(guò)上面的分析,我們知道啟動(dòng)StandardManager的第一步就是調(diào)用父類LifecycleBase的init方法,關(guān)于此方法已在《Tomcat源碼分析——生命周期管理》一文詳細(xì)介紹,所以我們只需要關(guān)心StandardManager的initInternal。StandardManager本身并沒(méi)有實(shí)現(xiàn)initInternal方法,但是StandardManager的父類ManagerBase實(shí)現(xiàn)了此方法,其實(shí)現(xiàn)見(jiàn)代碼清單3。
代碼清單3
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
setDistributable(((Context) getContainer()).getDistributable());
// Initialize random number generation
getRandomBytes(new byte[16]);
}
閱讀代碼清單3,我們總結(jié)下ManagerBase的initInternal方法的執(zhí)行步驟:
將容器自身即StandardManager注冊(cè)到JMX(LifecycleMBeanBase的initInternal方法的實(shí)現(xiàn)請(qǐng)參考《Tomcat源碼分析——生命周期管理》一文);
從父容器StandardContext中獲取當(dāng)前Tomcat是否是集群部署,并設(shè)置為ManagerBase的布爾屬性distributable;
調(diào)用getRandomBytes方法從隨機(jī)數(shù)文件/dev/urandom中獲取隨機(jī)數(shù)字節(jié)數(shù)組,如果不存在此文件則通過(guò)反射生成java.security.SecureRandom的實(shí)例,用它生成隨機(jī)數(shù)字節(jié)數(shù)組。
注意:此處調(diào)用getRandomBytes方法生成的隨機(jī)數(shù)字節(jié)數(shù)組并不會(huì)被使用,之所以在這里調(diào)用實(shí)際是為了完成對(duì)隨機(jī)數(shù)生成器的初始化,以便將來(lái)分配Session ID時(shí)使用。
我們?cè)敿?xì)閱讀下getRandomBytes方法的代碼實(shí)現(xiàn),見(jiàn)代碼清單4。
代碼清單4
protected void getRandomBytes(byte bytes[]) {
// Generate a byte array containing a session identifier
if (devRandomSource != null && randomIS == null) {
setRandomFile(devRandomSource);
}
if (randomIS != null) {
try {
int len = randomIS.read(bytes);
if (len == bytes.length) {
return;
}
if(log.isDebugEnabled())
log.debug("Got " + len + " " + bytes.length );
} catch (Exception ex) {
// Ignore
}
devRandomSource = null;
try {
randomIS.close();
} catch (Exception e) {
log.warn("Failed to close randomIS.");
}
randomIS = null;
}
getRandom().nextBytes(bytes);
}
代碼清單4中的setRandomFile
方法(見(jiàn)代碼清單5)用于從隨機(jī)數(shù)文件/dev/urandom中獲取隨機(jī)數(shù)字節(jié)數(shù)組。
代碼清單5
public void setRandomFile( String s ) {
// as a hack, you can use a static file - and generate the same
// session ids ( good for strange debugging )
if (Globals.IS_SECURITY_ENABLED){
randomIS = AccessController.doPrivileged(new PrivilegedSetRandomFile(s));
} else {
try{
devRandomSource=s;
File f=new File( devRandomSource );
if( ! f.exists() ) return;
randomIS= new DataInputStream( new FileInputStream(f));
randomIS.readLong();
if( log.isDebugEnabled() )
log.debug( "Opening " + devRandomSource );
} catch( IOException ex ) {
log.warn("Error reading " + devRandomSource, ex);
if (randomIS != null) {
try {
randomIS.close();
} catch (Exception e) {
log.warn("Failed to close randomIS.");
}
}
devRandomSource = null;
randomIS=null;
}
}
}
代碼清單4中的setRandomFile方法(見(jiàn)代碼清單6)通過(guò)反射生成java.security.SecureRandom的實(shí)例,并用此實(shí)例生成隨機(jī)數(shù)字節(jié)數(shù)組。
代碼清單6
public Random getRandom() {
if (this.random == null) {
// Calculate the new random number generator seed
long seed = System.currentTimeMillis();
long t1 = seed;
char entropy[] = getEntropy().toCharArray();
for (int i = 0; i < entropy.length; i++) {
long update = ((byte) entropy[i]) << ((i % 8) * 8);
seed ^= update;
}
try {
// Construct and seed a new random number generator
Class<?> clazz = Class.forName(randomClass);
this.random = (Random) clazz.newInstance();
this.random.setSeed(seed);
} catch (Exception e) {
// Fall back to the simple case
log.error(sm.getString("managerBase.random", randomClass),
e);
this.random = new java.util.Random();
this.random.setSeed(seed);
}
if(log.isDebugEnabled()) {
long t2=System.currentTimeMillis();
if( (t2-t1) > 100 )
log.debug(sm.getString("managerBase.seeding", randomClass) + " " + (t2-t1));
}
}
return (this.random);
}
根據(jù)以上的分析,StandardManager的初始化主要就是執(zhí)行了ManagerBase的initInternal方法。
STANDARDMANAGER的啟動(dòng)
調(diào)用StandardManager的startInternal方法用于啟動(dòng)StandardManager,見(jiàn)代碼清單7。
代碼清單7
@Override
protected synchronized void startInternal() throws LifecycleException {
// Force initialization of the random number generator
if (log.isDebugEnabled())
log.debug("Force random number initialization starting");
generateSessionId();
if (log.isDebugEnabled())
log.debug("Force random number initialization completed");
// Load unloaded sessions, if any
try {
load();
} catch (Throwable t) {
log.error(sm.getString("standardManager.managerLoad"), t);
}
setState(LifecycleState.STARTING);
}
從代碼清單7可以看出啟動(dòng)StandardManager的步驟如下:
步驟一 調(diào)用generateSessionId方法(見(jiàn)代碼清單8)生成新的Session ID;
代碼清單8
protected synchronized String generateSessionId() {
byte random[] = new byte[16];
String jvmRoute = getJvmRoute();
String result = null;
// Render the result as a String of hexadecimal digits
StringBuilder buffer = new StringBuilder();
do {
int resultLenBytes = 0;
if (result != null) {
buffer = new StringBuilder();
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);
}
步驟二 加載持久化的Session信息。為什么Session需要持久化?由于在StandardManager中,所有的Session都維護(hù)在一個(gè)ConcurrentHashMap中,因此服務(wù)器重啟或者宕機(jī)會(huì)造成這些Session信息丟失或失效,為了解決這個(gè)問(wèn)題,Tomcat將這些Session通過(guò)持久化的方式來(lái)保證不會(huì)丟失。下面我們來(lái)看看StandardManager的load方法的實(shí)現(xiàn),見(jiàn)代碼清單9所示。
代碼清單9
public void load() throws ClassNotFoundException, IOException {
if (SecurityUtil.isPackageProtectionEnabled()){
try{
AccessController.doPrivileged( new PrivilegedDoLoad() );
} catch (PrivilegedActionException ex){
Exception exception = ex.getException();
if (exception instanceof ClassNotFoundException){
throw (ClassNotFoundException)exception;
} else if (exception instanceof IOException){
throw (IOException)exception;
}
if (log.isDebugEnabled())
log.debug("Unreported exception in load() "
+ exception);
}
} else {
doLoad();
}
}
如果需要安全機(jī)制是打開(kāi)的并且包保護(hù)模式打開(kāi),會(huì)通過(guò)創(chuàng)建PrivilegedDoLoad來(lái)加載持久化的Session,其實(shí)現(xiàn)如代碼清單10所示。
代碼清單10
private class PrivilegedDoLoad
implements PrivilegedExceptionAction<Void> {
PrivilegedDoLoad() {
// NOOP
}
public Void run() throws Exception{
doLoad();
return null;
}
}
從代碼清單10看到實(shí)際負(fù)責(zé)加載的方法是doLoad,根據(jù)代碼清單9知道默認(rèn)情況下,加載Session信息的方法也是doLoad。所以我們只需要看看doLoad的實(shí)現(xiàn)了,見(jiàn)代碼清單11。
代碼清單11
protected void doLoad() throws ClassNotFoundException, IOException {
if (log.isDebugEnabled())
log.debug("Start: Loading persisted sessions");
// Initialize our internal data structures
sessions.clear();
// Open an input stream to the specified pathname, if any
File file = file();
if (file == null)
return;
if (log.isDebugEnabled())
log.debug(sm.getString("standardManager.loading", pathname));
FileInputStream fis = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
Loader loader = null;
ClassLoader classLoader = null;
try {
fis = new FileInputStream(file.getAbsolutePath());
bis = new BufferedInputStream(fis);
if (container != null)
loader = container.getLoader();
if (loader != null)
classLoader = loader.getClassLoader();
if (classLoader != null) {
if (log.isDebugEnabled())
log.debug("Creating custom object input stream for class loader ");
ois = new CustomObjectInputStream(bis, classLoader);
} else {
if (log.isDebugEnabled())
log.debug("Creating standard object input stream");
ois = new ObjectInputStream(bis);
}
} catch (FileNotFoundException e) {
if (log.isDebugEnabled())
log.debug("No persisted data file found");
return;
} catch (IOException e) {
log.error(sm.getString("standardManager.loading.ioe", e), e);
if (fis != null) {
try {
fis.close();
} catch (IOException f) {
// Ignore
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException f) {
// Ignore
}
}
throw e;
}
// Load the previously unloaded active sessions
synchronized (sessions) {
try {
Integer count = (Integer) ois.readObject();
int n = count.intValue();
if (log.isDebugEnabled())
log.debug("Loading " + n + " persisted sessions");
for (int i = 0; i < n; i++) {
StandardSession session = getNewSession();
session.readObjectData(ois);
session.setManager(this);
sessions.put(session.getIdInternal(), session);
session.activate();
if (!session.isValidInternal()) {
// If session is already invalid,
// expire session to prevent memory leak.
session.setValid(true);
session.expire();
}
sessionCounter++;
}
} catch (ClassNotFoundException e) {
log.error(sm.getString("standardManager.loading.cnfe", e), e);
try {
ois.close();
} catch (IOException f) {
// Ignore
}
throw e;
} catch (IOException e) {
log.error(sm.getString("standardManager.loading.ioe", e), e);
try {
ois.close();
} catch (IOException f) {
// Ignore
}
throw e;
} finally {
// Close the input stream
try {
ois.close();
} catch (IOException f) {
// ignored
}
// Delete the persistent storage file
if (file.exists() )
file.delete();
}
}
if (log.isDebugEnabled())
log.debug("Finish: Loading persisted sessions");
}
從代碼清單11看到StandardManager的doLoad方法的執(zhí)行步驟如下:
清空sessions緩存維護(hù)的Session信息;
調(diào)用file方法返回當(dāng)前Context下的Session持久化文件,比如:D:\workspace\Tomcat7.0\work\Catalina\localhost\host-manager\SESSIONS.ser;
打開(kāi)Session持久化文件的輸入流,并封裝為CustomObjectInputStream;
從Session持久化文件讀入持久化的Session的數(shù)量,然后逐個(gè)讀取Session信息并放入sessions緩存中。
至此,有關(guān)StandardManager的啟動(dòng)就介紹到這里,我將會(huì)在下篇內(nèi)容講解Session的分配、追蹤、銷毀等內(nèi)容。
- Tomcat實(shí)現(xiàn)session共享(session 會(huì)話復(fù)制)
- Tomcat集群和Session復(fù)制應(yīng)用介紹
- nginx+tomcat實(shí)現(xiàn)負(fù)載均衡,使用redis session共享
- Tomcat中session的管理機(jī)制
- Java中tomcat memecached session 共享同步問(wèn)題的解決辦法
- Tomcat中實(shí)現(xiàn)Session小結(jié)
- 淺談Tomcat Session管理分析
- Nginx+Tomcat關(guān)于Session的管理的實(shí)現(xiàn)
- Tomcat如何監(jiān)控并刪除超時(shí)Session詳解
- Tomcat中的Session與Cookie深入講解
相關(guān)文章
詳解Spring Boot中使用AOP統(tǒng)一處理Web請(qǐng)求日志
本篇文章主要介紹了詳解Spring Boot中使用AOP統(tǒng)一處理Web請(qǐng)求日志,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
淺談s:select 標(biāo)簽中l(wèi)ist存放map對(duì)象的使用
下面小編就為大家?guī)?lái)一篇淺談s:select 標(biāo)簽中l(wèi)ist存放map對(duì)象的使用。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11
解決Spring在Thread中注入Bean無(wú)效的問(wèn)題
這篇文章主要介紹了解決Spring在Thread中注入Bean無(wú)效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Java的Netty進(jìn)階之Future和Promise詳解
這篇文章主要介紹了Java的Netty進(jìn)階之Future和Promise詳解,Netty 是基于 Java NIO 的異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架,使用 Netty 可以快速開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用,Netty 提供了高層次的抽象來(lái)簡(jiǎn)化 TCP 和 UDP 服務(wù)器的編程,但是你仍然可以使用底層的 API,需要的朋友可以參考下2023-11-11
hashMap擴(kuò)容時(shí)應(yīng)該注意這些死循環(huán)問(wèn)題
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著hashMap擴(kuò)容時(shí)的死循環(huán)問(wèn)題展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Java使用RedisTemplate如何根據(jù)前綴獲取key列表
這篇文章主要介紹了Java使用RedisTemplate如何根據(jù)前綴獲取key列表,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06

