Tomcat7.0源码分析——Session管理分析(上)

http://blog.csdn.net/beliefer/article/details/52450268

前言

  对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录、身份、权限及状态等信息。对于使用Tomcat作为Web容器的大部分开发人员而言,Tomcat是如何实现Session标记用户和管理Session信息的呢?

概述

Session

  Tomcat内部定义了Session和HttpSession这两个会话相关的接口,其类继承体系如图1所示。


图1

  Session类继承体系图1中额外列出了Session的类继承体系,这里对他们逐个进行介绍。

Session:Tomcat中有关会话的基本接口规范,图1列出了它定义的主要方法,表1对这些方法进行介绍。

表1  Session接口说明

方法描述
getCreationTime()/setCreationTime(time : long)获取与设置Session的创建时间
getId()/setId(id : String)获取与设置Session的ID
getThisAccessedTime()获取最近一次请求的开始时间
getLastAccessedTime()获取最近一次请求的完成时间
getManager()/setManager(manager : Manager)获取与设置Session管理器
getMaxInactiveInterval()/setMaxInactiveInterval(interval : int)获取与设置Session的最大访问间隔
getSession()获取HttpSession
isValid()/setValid(isValid : boolean)获取与设置Session的有效状态
access()/endAccess()开始与结束Session的访问
expire()设置Session过期

HttpSession:在HTTP客户端与HTTP服务端提供的一种会话的接口规范,图1列出了它定义的主要方法,表2对这些方法进行介绍。

表2  HttpSession接口说明

方法描述
getCreationTime()获取Session的创建时间
getId()获取Session的ID
getLastAccessedTime()获取最近一次请求的完成时间
getServletContext() 获取当前Session所属的ServletContext
getMaxInactiveInterval()/setMaxInactiveInterval(interval : int)获取与设置Session的最大访问间隔
getAttribute(name : String) /setAttribute(name : String, value : Object)获取与设置Session作用域的属性
removeAttribute(name : String)清除Session作用域的属性
invalidate()使Session失效并解除任何与此Session绑定的对象

ClusterSession:集群部署下的会话接口规范,图1列出了它的主要方法,表3对这些方法进行介绍。

表3  ClusterSession接口说明

方法描述
isPrimarySession()是否是集群的主Session
setPrimarySession(boolean primarySession)设置集群主Session

StandardSession:标准的HTTP Session实现,本文将以此实现为例展开。

在部署Tomcat集群时,需要使集群中各个节点的会话状态保持同步,目前Tomcat提供了两种同步策略:

  • ReplicatedSession:每次都把整个会话对象同步给集群中的其他节点,其他节点然后更新整个会话对象。这种实现比较简单方便,但会造成大量无效信息的传输。
  • DeltaSession:对会话中增量修改的属性进行同步。这种方式由于是增量的,所以会大大降低网络I/O的开销,但是实现上会比较复杂因为涉及到对会话属性操作过程的管理。

Session管理器

  Tomcat内部定义了Manager接口用于制定Session管理器的接口规范,目前已经有很多Session管理器的实现,如图2所示。


图2  Session管理器的类继承体系

对应图2中的内容我们下面逐个描述:

Manager:Tomcat对于Session管理器定义的接口规范,图2已经列出了Manager接口中定义的主要方法,表4详细描述了这些方法的作用。

表4  Manager接口说明

方法描述
getContainer()
setContainer(container : Container)
获取或设置Session管理器关联的容器,一般为Context容器
getDistributable()
setDistributable(distributable : boolean)
获取或设置Session管理器是否支持分布式
getMaxInactiveInterval()
setMaxInactiveInterval(interval : int)
获取或设置Session管理器创建的Session的最大非活动时间间隔
getSessionIdLength()
setSessionIdLength(idLength : int)
获取或设置Session管理器创建的Session ID的长度
getSessionCounter()
setSessionCounter(sessionCounter : long)
获取或设置Session管理器创建的Session总数
getMaxActive()
setMaxActive(maxActive : int)
获取或设置当前已激活Session的最大数量
getActiveSessions()获取当前激活的所有Session
getExpiredSessions()
setExpiredSessions(expiredSessions : long)
获取或设置当前已过期Session的数量
getRejectedSessions()
setRejectedSessions(rejectedSessions : int)
获取或设置已拒绝创建Session的数量
getSessionMaxAliveTime()
setSessionMaxAliveTime(sessionMaxAliveTime : int)
获取或设置已过期Session中的最大活动时长
getSessionAverageAliveTime()
setSessionAverageAliveTime(sessionAverageAliveTime : int)
获取或设置已过期Session的平均活动时长
add(session : Session)
remove(session : Session)
给Session管理器增加或删除活动Session
changeSessionId(session : Session)给Session设置新生成的随机Session ID
createSession(sessionId : String)基于Session管理器的默认属性配置创建新的Session
findSession(id : String)返回sessionId参数唯一标记的Session
findSessions()返回Session管理器管理的所有活动Session
load()
unload()
从持久化机制中加载Session或向持久化机制写入Session
backgroundProcess()容器接口中定义的为具体容器在后台处理相关工作的实现,Session管理器基于此机制实现了过期Session的销毁

ManagerBase:封装了Manager接口通用实现的抽象类,未提供对load()/unload()等方法的实现,需要具体子类去实现。所有的Session管理器都继承自ManagerBase。

ClusterManager:在Manager接口的基础上增加了集群部署下的一些接口,所有实现集群下Session管理的管理器都需要实现此接口。

PersistentManagerBase:提供了对于Session持久化的基本实现。

PersistentManager:继承自PersistentManagerBase,可以在Server.xml的元素下通过配置元素来使用。PersistentManager可以将内存中的Session信息备份到文件或数据库中。当备份一个Session对象时,该Session对象会被复制到存储器(文件或者数据库)中,而原对象仍然留在内存中。因此即便服务器宕机,仍然可以从存储器中获取活动的Session对象。如果活动的Session对象超过了上限值或者Session对象闲置了的时间过长,那么Session会被换出到存储器中以节省内存空间。 

StandardManager:不用配置元素,当Tomcat正常关闭,重启或Web应用重新加载时,它会将内存中的Session序列化到Tomcat目录下的/work/Catalina/host_name/webapp_name/SESSIONS.ser文件中。当Tomcat重启或应用加载完成后,Tomcat会将文件中的Session重新还原到内存中。如果突然终止该服务器,则所有Session都将丢失,因为StandardManager没有机会实现存盘处理。

ClusterManagerBase:提供了对于Session的集群管理实现。

DeltaManager:继承自ClusterManagerBase。此Session管理器是Tomcat在集群部署下的默认管理器,当集群中的某一节点生成或修改Session后,DeltaManager将会把这些修改增量复制到其他节点。

BackupManager:没有继承ClusterManagerBase,而是直接实现了ClusterManager接口。是Tomcat在集群部署下的可选的Session管理器,集群中的所有Session都被全量复制到一个备份节点。集群中的所有节点都可以访问此备份节点,达到Session在集群下的备份效果。

  为简单起见,本文以StandardManager为例讲解Session的管理。StandardManager是StandardContext的子组件,用来管理当前Context的所有Session的创建和维护。如果你已经阅读或者熟悉了《Tomcat7.0源码分析——生命周期管理》一文的内容,那么你就知道当StandardContext正式启动,也就是StandardContext的startInternal方法(见代码清单1)被调用时,StandardContext还会启动StandardManager。

代码清单1

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. protected synchronized void startInternal() throws LifecycleException {  
  3.              
  4.     // 省略与Session管理无关的代码  
  5.                      
  6.             // Acquire clustered manager  
  7.             Manager contextManager = null;  
  8.             if (manager == null) {  
  9.                 if ( (getCluster() != null) && distributable) {  
  10.                     try {  
  11.                         contextManager = getCluster().createManager(getName());  
  12.                     } catch (Exception ex) {  
  13.                         log.error("standardContext.clusterFail", ex);  
  14.                         ok = false;  
  15.                     }  
  16.                 } else {  
  17.                     contextManager = new StandardManager();  
  18.                 }  
  19.             }   
  20.               
  21.             // Configure default manager if none was specified  
  22.             if (contextManager != null) {  
  23.                 setManager(contextManager);  
  24.             }  
  25.   
  26.             if (manager!=null && (getCluster() != null) && distributable) {  
  27.                 //let the cluster know that there is a context that is distributable  
  28.                 //and that it has its own manager  
  29.                 getCluster().registerManager(manager);  
  30.             }  
  31.  // 省略与Session管理无关的代码  
  32.           
  33.         try {  
  34.             // Start manager  
  35.             if ((manager != null) && (manager instanceof Lifecycle)) {  
  36.                 ((Lifecycle) getManager()).start();  
  37.             }  
  38.   
  39.             // Start ContainerBackgroundProcessor thread  
  40.             super.threadStart();  
  41.         } catch(Exception e) {  
  42.             log.error("Error manager.start()", e);  
  43.             ok = false;  
  44.         }  
  45.           
  46.  // 省略与Session管理无关的代码  
  47. }  

从代码清单1可以看到StandardContext的startInternal方法中涉及Session管理的执行步骤如下:

  1. 创建StandardManager;
  2. 如果Tomcat结合Apache做了分布式部署,会将当前StandardManager注册到集群中;
  3. 启动StandardManager;
StandardManager的start方法用于启动StandardManager,实现见代码清单2。

代码清单2

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public synchronized final void start() throws LifecycleException {  
  3.       
  4.     //省略状态校验的代码if (state.equals(LifecycleState.NEW)) {  
  5.         init();  
  6.     } else if (!state.equals(LifecycleState.INITIALIZED) &&  
  7.             !state.equals(LifecycleState.STOPPED)) {  
  8.         invalidTransition(Lifecycle.BEFORE_START_EVENT);  
  9.     }  
  10.   
  11.     setState(LifecycleState.STARTING_PREP);  
  12.   
  13.     try {  
  14.         startInternal();  
  15.     } catch (LifecycleException e) {  
  16.         setState(LifecycleState.FAILED);  
  17.         throw e;  
  18.     }  
  19.   
  20.     if (state.equals(LifecycleState.FAILED) ||  
  21.             state.equals(LifecycleState.MUST_STOP)) {  
  22.         stop();  
  23.     } else {  
  24.         // Shouldn't be necessary but acts as a check that sub-classes are  
  25.         // doing what they are supposed to.  
  26.         if (!state.equals(LifecycleState.STARTING)) {  
  27.             invalidTransition(Lifecycle.AFTER_START_EVENT);  
  28.         }  
  29.           
  30.         setState(LifecycleState.STARTED);  
  31.     }  
  32. }  

从代码清单2可以看出启动StandardManager的步骤如下:

  1. 调用init方法初始化StandardManager;
  2. 调用startInternal方法启动StandardManager;

StandardManager的初始化

   经过上面的分析,我们知道启动StandardManager的第一步就是调用父类LifecycleBase的init方法,关于此方法已在《Tomcat7.0源码分析——生命周期管理》一文详细介绍,所以我们只需要关心StandardManager的initInternal。StandardManager本身并没有实现initInternal方法,但是StandardManager的父类ManagerBase实现了此方法,其实现见代码清单3。

代码清单3

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. protected void initInternal() throws LifecycleException {  
  3.       
  4.     super.initInternal();  
  5.       
  6.     setDistributable(((Context) getContainer()).getDistributable());  
  7.   
  8.     // Initialize random number generation  
  9.     getRandomBytes(new byte[16]);  
  10. }  

阅读代码清单3,我们总结下ManagerBase的initInternal方法的执行步骤:

  1. 将容器自身即StandardManager注册到JMX(LifecycleMBeanBase的initInternal方法的实现请参考《Tomcat7.0源码分析——生命周期管理》一文);
  2. 从父容器StandardContext中获取当前Tomcat是否是集群部署,并设置为ManagerBase的布尔属性distributable;
  3. 调用getRandomBytes方法从随机数文件/dev/urandom中获取随机数字节数组,如果不存在此文件则通过反射生成java.security.SecureRandom的实例,用它生成随机数字节数组。
注意:此处调用getRandomBytes方法生成的随机数字节数组并不会被使用,之所以在这里调用实际是为了完成对随机数生成器的初始化,以便将来分配Session ID时使用。我们详细阅读下getRandomBytes方法的代码实现,见代码清单4。

代码清单4

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. protected void getRandomBytes(byte bytes[]) {  
  2.     // Generate a byte array containing a session identifier  
  3.     if (devRandomSource != null && randomIS == null) {  
  4.         setRandomFile(devRandomSource);  
  5.     }  
  6.     if (randomIS != null) {  
  7.         try {  
  8.             int len = randomIS.read(bytes);  
  9.             if (len == bytes.length) {  
  10.                 return;  
  11.             }  
  12.             if(log.isDebugEnabled())  
  13.                 log.debug("Got " + len + " " + bytes.length );  
  14.         } catch (Exception ex) {  
  15.             // Ignore  
  16.         }  
  17.         devRandomSource = null;  
  18.           
  19.         try {  
  20.             randomIS.close();  
  21.         } catch (Exception e) {  
  22.             log.warn("Failed to close randomIS.");  
  23.         }  
  24.           
  25.         randomIS = null;  
  26.     }  
  27.     getRandom().nextBytes(bytes);  
  28. }  

代码清单4中的setRandomFile方法(见代码清单5)用于从随机数文件/dev/urandom中获取随机数字节数组。

代码清单5

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void setRandomFile( String s ) {  
  2.     // as a hack, you can use a static file - and generate the same  
  3.     // session ids ( good for strange debugging )  
  4.     if (Globals.IS_SECURITY_ENABLED){  
  5.         randomIS = AccessController.doPrivileged(new PrivilegedSetRandomFile(s));  
  6.     } else {  
  7.         try{  
  8.             devRandomSource=s;  
  9.             File f=new File( devRandomSource );  
  10.             if( ! f.exists() ) return;  
  11.             randomIS= new DataInputStream( new FileInputStream(f));  
  12.             randomIS.readLong();  
  13.             if( log.isDebugEnabled() )  
  14.                 log.debug( "Opening " + devRandomSource );  
  15.         } catch( IOException ex ) {  
  16.             log.warn("Error reading " + devRandomSource, ex);  
  17.             if (randomIS != null) {  
  18.                 try {  
  19.                     randomIS.close();  
  20.                 } catch (Exception e) {  
  21.                     log.warn("Failed to close randomIS.");  
  22.                 }  
  23.             }  
  24.             devRandomSource = null;  
  25.             randomIS=null;  
  26.         }  
  27.     }  
  28. }  

代码清单4中的getRandom方法(见代码清单6)通过反射生成java.security.SecureRandom的实例,并用此实例生成随机数字节数组。

代码清单6

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public Random getRandom() {  
  2.     if (this.random == null) {  
  3.         // Calculate the new random number generator seed  
  4.         long seed = System.currentTimeMillis();  
  5.         long t1 = seed;  
  6.         char entropy[] = getEntropy().toCharArray();  
  7.         for (int i = 0; i < entropy.length; i++) {  
  8.             long update = ((byte) entropy[i]) << ((i % 8) * 8);  
  9.             seed ^= update;  
  10.         }  
  11.         try {  
  12.             // Construct and seed a new random number generator  
  13.             Class<?> clazz = Class.forName(randomClass);  
  14.             this.random = (Random) clazz.newInstance();  
  15.             this.random.setSeed(seed);  
  16.         } catch (Exception e) {  
  17.             // Fall back to the simple case  
  18.             log.error(sm.getString("managerBase.random", randomClass),  
  19.                     e);  
  20.             this.random = new java.util.Random();  
  21.             this.random.setSeed(seed);  
  22.         }  
  23.         if(log.isDebugEnabled()) {  
  24.             long t2=System.currentTimeMillis();  
  25.             if( (t2-t1) > 100 )  
  26.                 log.debug(sm.getString("managerBase.seeding", randomClass) + " " + (t2-t1));  
  27.         }  
  28.     }  
  29.       
  30.     return (this.random);  
  31.   
  32. }  

根据以上的分析,StandardManager的初始化主要就是执行了ManagerBase的initInternal方法。

StandardManager的启动

  调用StandardManager的startInternal方法用于启动StandardManager,见代码清单7。

代码清单7

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. protected synchronized void startInternal() throws LifecycleException {  
  3.   
  4.     // Force initialization of the random number generator  
  5.     if (log.isDebugEnabled())  
  6.         log.debug("Force random number initialization starting");  
  7.     generateSessionId();  
  8.     if (log.isDebugEnabled())  
  9.         log.debug("Force random number initialization completed");  
  10.   
  11.     // Load unloaded sessions, if any  
  12.     try {  
  13.         load();  
  14.     } catch (Throwable t) {  
  15.         log.error(sm.getString("standardManager.managerLoad"), t);  
  16.     }  
  17.   
  18.     setState(LifecycleState.STARTING);  
  19. }  

从代码清单7可以看出启动StandardManager的步骤如下:

步骤一 调用generateSessionId方法(见代码清单8)强制初始化随机数生成器;

注意:此处调用generateSessionId方法的目的不是为了生成Session ID,而是为了强制初始化随机数生成器。

代码清单8

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. protected synchronized String generateSessionId() {  
  2.   
  3.     byte random[] = new byte[16];  
  4.     String jvmRoute = getJvmRoute();  
  5.     String result = null;  
  6.   
  7.     // Render the result as a String of hexadecimal digits  
  8.     StringBuilder buffer = new StringBuilder();  
  9.     do {  
  10.         int resultLenBytes = 0;  
  11.         if (result != null) {  
  12.             buffer = new StringBuilder();  
  13.             duplicates++;  
  14.         }  
  15.   
  16.         while (resultLenBytes < this.sessionIdLength) {  
  17.             getRandomBytes(random);  
  18.             random = getDigest().digest(random);  
  19.             for (int j = 0;  
  20.             j < random.length && resultLenBytes < this.sessionIdLength;  
  21.             j++) {  
  22.                 byte b1 = (byte) ((random[j] & 0xf0) >> 4);  
  23.                 byte b2 = (byte) (random[j] & 0x0f);  
  24.                 if (b1 < 10)  
  25.                     buffer.append((char) ('0' + b1));  
  26.                 else  
  27.                     buffer.append((char) ('A' + (b1 - 10)));  
  28.                 if (b2 < 10)  
  29.                     buffer.append((char) ('0' + b2));  
  30.                 else  
  31.                     buffer.append((char) ('A' + (b2 - 10)));  
  32.                 resultLenBytes++;  
  33.             }  
  34.         }  
  35.         if (jvmRoute != null) {  
  36.             buffer.append('.').append(jvmRoute);  
  37.         }  
  38.         result = buffer.toString();  
  39.     } while (sessions.containsKey(result));  
  40.     return (result);  
  41.   
  42. }  

步骤二 加载持久化的Session信息。为什么Session需要持久化?由于在StandardManager中,所有的Session都维护在一个ConcurrentHashMap中,因此服务器重启或者宕机会造成这些Session信息丢失或失效,为了解决这个问题,Tomcat将这些Session通过持久化的方式来保证不会丢失。下面我们来看看StandardManager的load方法的实现,见代码清单9所示。

代码清单9

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void load() throws ClassNotFoundException, IOException {  
  2.     if (SecurityUtil.isPackageProtectionEnabled()){  
  3.         try{  
  4.             AccessController.doPrivileged( new PrivilegedDoLoad() );  
  5.         } catch (PrivilegedActionException ex){  
  6.             Exception exception = ex.getException();  
  7.             if (exception instanceof ClassNotFoundException){  
  8.                 throw (ClassNotFoundException)exception;  
  9.             } else if (exception instanceof IOException){  
  10.                 throw (IOException)exception;  
  11.             }  
  12.             if (log.isDebugEnabled())  
  13.                 log.debug("Unreported exception in load() "  
  14.                     + exception);  
  15.         }  
  16.     } else {  
  17.         doLoad();  
  18.     }  
  19. }  

如果需要安全机制是打开的并且包保护模式打开,会通过创建PrivilegedDoLoad来加载持久化的Session,其实现如代码清单10所示。

代码清单10

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private class PrivilegedDoLoad  
  2.     implements PrivilegedExceptionAction<Void> {  
  3.   
  4.     PrivilegedDoLoad() {  
  5.         // NOOP  
  6.     }  
  7.   
  8.     public Void run() throws Exception{  
  9.        doLoad();  
  10.        return null;  
  11.     }  
  12. }  

从代码清单10看到实际负责加载的方法是doLoad,根据代码清单9知道默认情况下,加载Session信息的方法也是doLoad。所以我们只需要看看doLoad的实现了,见代码清单11。代码清单11

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. protected void doLoad() throws ClassNotFoundException, IOException {  
  2.     if (log.isDebugEnabled())  
  3.         log.debug("Start: Loading persisted sessions");  
  4.   
  5.     // Initialize our internal data structures  
  6.     sessions.clear();  
  7.   
  8.     // Open an input stream to the specified pathname, if any  
  9.     File file = file();  
  10.     if (file == null)  
  11.         return;  
  12.     if (log.isDebugEnabled())  
  13.         log.debug(sm.getString("standardManager.loading", pathname));  
  14.     FileInputStream fis = null;  
  15.     BufferedInputStream bis = null;  
  16.     ObjectInputStream ois = null;  
  17.     Loader loader = null;  
  18.     ClassLoader classLoader = null;  
  19.     try {  
  20.         fis = new FileInputStream(file.getAbsolutePath());  
  21.         bis = new BufferedInputStream(fis);  
  22.         if (container != null)  
  23.             loader = container.getLoader();  
  24.         if (loader != null)  
  25.             classLoader = loader.getClassLoader();  
  26.         if (classLoader != null) {  
  27.             if (log.isDebugEnabled())  
  28.                 log.debug("Creating custom object input stream for class loader ");  
  29.             ois = new CustomObjectInputStream(bis, classLoader);  
  30.         } else {  
  31.             if (log.isDebugEnabled())  
  32.                 log.debug("Creating standard object input stream");  
  33.             ois = new ObjectInputStream(bis);  
  34.         }  
  35.     } catch (FileNotFoundException e) {  
  36.         if (log.isDebugEnabled())  
  37.             log.debug("No persisted data file found");  
  38.         return;  
  39.     } catch (IOException e) {  
  40.         log.error(sm.getString("standardManager.loading.ioe", e), e);  
  41.         if (fis != null) {  
  42.             try {  
  43.                 fis.close();  
  44.             } catch (IOException f) {  
  45.                 // Ignore  
  46.             }  
  47.         }  
  48.         if (bis != null) {  
  49.             try {  
  50.                 bis.close();  
  51.             } catch (IOException f) {  
  52.                 // Ignore  
  53.             }  
  54.         }  
  55.         throw e;  
  56.     }  
  57.   
  58.     // Load the previously unloaded active sessions  
  59.     synchronized (sessions) {  
  60.         try {  
  61.             Integer count = (Integer) ois.readObject();  
  62.             int n = count.intValue();  
  63.             if (log.isDebugEnabled())  
  64.                 log.debug("Loading " + n + " persisted sessions");  
  65.             for (int i = 0; i < n; i++) {  
  66.                 StandardSession session = getNewSession();  
  67.                 session.readObjectData(ois);  
  68.                 session.setManager(this);  
  69.                 sessions.put(session.getIdInternal(), session);  
  70.                 session.activate();  
  71.                 if (!session.isValidInternal()) {  
  72.                     // If session is already invalid,  
  73.                     // expire session to prevent memory leak.  
  74.                     session.setValid(true);  
  75.                     session.expire();  
  76.                 }  
  77.                 sessionCounter++;  
  78.             }  
  79.         } catch (ClassNotFoundException e) {  
  80.             log.error(sm.getString("standardManager.loading.cnfe", e), e);  
  81.             try {  
  82.                 ois.close();  
  83.             } catch (IOException f) {  
  84.                 // Ignore  
  85.             }  
  86.             throw e;  
  87.         } catch (IOException e) {  
  88.             log.error(sm.getString("standardManager.loading.ioe", e), e);  
  89.             try {  
  90.                 ois.close();  
  91.             } catch (IOException f) {  
  92.                 // Ignore  
  93.             }  
  94.             throw e;  
  95.         } finally {  
  96.             // Close the input stream  
  97.             try {  
  98.                 ois.close();  
  99.             } catch (IOException f) {  
  100.                 // ignored  
  101.             }  
  102.   
  103.             // Delete the persistent storage file  
  104.             if (file.exists() )  
  105.                 file.delete();  
  106.         }  
  107.     }  
  108.   
  109.     if (log.isDebugEnabled())  
  110.         log.debug("Finish: Loading persisted sessions");  
  111. }  

从代码清单11看到StandardManager的doLoad方法的执行步骤如下:

  1. 清空sessions缓存维护的Session信息;
  2. 调用file方法返回当前Context下的Session持久化文件,比如:D:\workspace\Tomcat7.0\work\Catalina\localhost\host-manager\SESSIONS.ser;
  3. 打开Session持久化文件的输入流,并封装为CustomObjectInputStream;
  4. 从Session持久化文件读入持久化的Session的数量,然后逐个读取Session信息并放入sessions缓存中。
至此,有关StandardManager的启动就介绍到这里,我将会在 《Tomcat7.0源码分析——Session管理分析(下)》一文讲解Session的分配、追踪、销毁等内容。


后记:个人总结整理的《深入理解Spark:核心思想与源码分析》一书现在已经正式出版上市,目前京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买。


京东:http://item.jd.com/11846120.html 

当当:http://product.dangdang.com/23838168.html 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值