tomcat(9)Session管理

【0】README
0.0)本文部分描述转自“深入剖析tomcat”,旨在学习tomcat-Session管理” 的基础知识;
0.1)Catalina通过一个称为Session 管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口来表示。(干货——catalina通过Session管理器组件来管理Session对象)
0.2)Session管理器:需要与一个Context容器相关联,且必须与一个Context容器关联;(干货——intro to Session Manager)
0.3)Session管理器:负责创建,更新,销毁Session对象,当有请求到来时,要会返回一个有效的Session对象;
0.4)default case:Session管理器会将其所管理的 Session对象存放在内存中。但在tomcat中,Session管理器也可以将Session 对象进行持久化,存储到文件存储器或通过jdbc写入到database;
0.5)for complete source code, please visit  https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter9;

【1】Session对象
1)intro to Session object:Session对象由 javax.servlet.http.HttpSession接口表示;
2)UML类图实例:

【1.1】Session接口
1)Session接口是作为 Catalina内部的外观类使用的。Session 接口的标准实现 StandardSession类也实现了 javax.servlet.http.HttpSession 接口;
public interface Session {  public static final String SESSION_CREATED_EVENT = "createSession";  public static final String SESSION_DESTROYED_EVENT = "destroySession";  public String getAuthType();    public void setAuthType(String authType);  public long getCreationTime();  public void setCreationTime(long time);  public String getId();  public void setId(String id);    public String getInfo();  public long getLastAccessedTime();  public Manager getManager();    public void setManager(Manager manager);  public int getMaxInactiveInterval();   public void setMaxInactiveInterval(int interval);     public void setNew(boolean isNew);  public Principal getPrincipal();    public void setPrincipal(Principal principal);  public HttpSession getSession();  public void setValid(boolean isValid);    public boolean isValid();  public void access();   public void addSessionListener(SessionListener listener);  public void expire();     public Object getNote(String name);  public Iterator getNoteNames();  public void recycle();public void removeNote(String name);public void removeSessionListener(SessionListener listener);public void setNote(String name, Object value); 
}
2)Session对象总是存在于Session管理器中的:可以使用 getManager()方法或 setManager() 方法将Session实例和某个 Session管理器相关联;
2.1)对某个Session实例来说:在与其Session 管理器相关联的某个Context实例内,该Session对象有一个唯一的标识符,可以通过 setId() 方法和 getId() 方法来访问该 Session 标识符;
2.2)Session管理器会调用 getLastAccessedTime()方法:根据返回值来判断一个Session对象的有效性;(Session管理器会调用setValid()方法来设置或重置其有效性)
2.3)每当访问一个Session实例时: 会调用其 access()方法 来修改 Session对象的最后访问时间;
2.4)最后:Session管理器会调用Session对象的 expire()方法将其设置为过期, 也可以通过getSession() 方法获取一个经过 Session 外观类包装的 HttpSession 对象;

【1.2】StandardSession类
1)StandardSession类实现了 java.lang.Serilizable接口,便于序列化Session对象;(干货——StandardSession类实现了 java.lang.Serilizable接口,便于序列化Session对象)
2)StandardSession类的构造函数接收Manager接口的一个实例:迫使一个Session对象必须拥有一个Session管理器实例;(干货——注意迫使二字)
<span style="font-size:18px;">public StandardSession(Manager manager);</span>
3)下面是StandardSession实例的一些比较重要的私有变量,用于维护该StandardSession实例的一些状态。注意,带有transient关键字的变量是无法序列化的;
Attention)在tomcat5中,上述变量是受保护的,而tomcat4中,它们都是私有变量。每个变量都有与之对应的一个访问器和一个转变器(修改器)(get 和 set 方法)
class StandardSessionimplements HttpSession, Session, Serializable { public StandardSession(Manager manager) { super();this.manager = manager;if (manager instanceof ManagerBase)this.debug = ((ManagerBase) manager).getDebug();}private static final String NOT_SERIALIZED ="___NOT_SERIALIZABLE_EXCEPTION___"; private HashMap attributes = new HashMap();private transient String authType = null; private transient Method containerEventMethod = null; private static final Class containerEventTypes[] = { String.class, Object.class }; private long creationTime = 0L;   private transient int debug = 0;private transient boolean expiring = false;private transient StandardSessionFacade facade = null;private String id = null;private static final String info = "StandardSession/1.0";private long lastAccessedTime = creationTime;private transient ArrayList listeners = new ArrayList(); private Manager manager = null;private int maxInactiveInterval = -1;private boolean isNew = false;private boolean isValid = false;private transient HashMap notes = new HashMap();private transient Principal principal = null;private static StringManager sm =StringManager.getManager(Constants.Package); private static HttpSessionContext sessionContext = null;private transient PropertyChangeSupport support =new PropertyChangeSupport(this); private long lastUsedTime = creationTime;
}
4)其中 getSession()方法会通过一个自身实例来创建 StandardSessionFacade 类的一个实例,并将其返回;(干货——如果是单例模式的话,该方法写的就很有问题,当然只是小生我一个小小的猜想)
public HttpSession getSession() {if(facade==null){facade = new StandardSessionFacade(this);}return facade;
}
5)设置Session为过期:若Session管理器中的某个Session对象 在某个时间长度内都没有被访问 的话,会被 Session 管理器设置为过期,这个时间长度由变量 maxInactiveInterval 的值来指定;(干货——将一个Session对象设置为过期主要是通过Session接口的expire() 方法来完成的);
 public void expire(boolean notify) {// Mark this session as "being expired" if neededif (expiring)return;expiring = true;setValid(false);// Remove this session from our manager's active sessionsif (manager != null)manager.remove(this);// Unbind any objects associated with this sessionString keys[] = keys();for (int i = 0; i < keys.length; i++)removeAttribute(keys[i], notify);// Notify interested session event listenersif (notify) {fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);}// Notify interested application event listeners// FIXME - Assumes we call listeners in reverse orderContext context = (Context) manager.getContainer();Object listeners[] = context.getApplicationListeners();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 {fireContainerEvent(context, // highlight line."beforeSessionDestroyed",listener);listener.sessionDestroyed(event); // destroy session.fireContainerEvent(context, // also highlight line."afterSessionDestroyed",listener);} catch (Throwable t) {try {fireContainerEvent(context,"afterSessionDestroyed",listener);} catch (Exception e) {;}// FIXME - should we do anything besides log these?log(sm.getString("standardSession.sessionEvent"), t);}}}// We have completed expire of this sessionexpiring = false;if ((manager != null) && (manager instanceof ManagerBase)) {recycle();}    }

 private void fireContainerEvent(Context context,String type, Object data)throws Exception {if (!"org.apache.catalina.core.StandardContext".equals(context.getClass().getName())) {return; // Container events are not supported}// NOTE:  Race condition is harmless, so do not synchronizeif (containerEventMethod == null) {containerEventMethod =context.getClass().getMethod("fireContainerEvent", // terrific line.containerEventTypes);}Object containerEventParams[] = new Object[2];containerEventParams[0] = type;containerEventParams[1] = data;containerEventMethod.invoke(context, containerEventParams);}

【1.3】StandardSessionFacade类
1)为了传递一个 Session对象给servlet实例,Catalina会实例化 StandardSession 类,填充该Session对象,然后再将其传给 servlet实例;(干货——但是,请注意下面的但是)
2)但是(呵呵):Catalina传递的是Session的 外观类 StandardSessionFacade的实例,该类仅仅实现了 javax.servlet.http.HttpSession接口中的方法;
3)这样,servlet程序员就不能将 HttpSession对象向下转换为  StandardSession 类型,也阻止了 servlet程序员访问一些敏感方法;

【2】Manager
1)Catalina通过一个称为Session 管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口来表示。
2)Catalina中:org.apache.catalina.session包中有一个名为 ManagerBase的工具类,该类提供了常见功能的实现。ManagerBase类有两个直接子类,分别是 StandardManager 类 和 PersistentManagerBase类;(干货——引入ManagerBase的工具类)
3)当Catalina运行时:StandardManager实例会将Session对象存储在内存中,但是,当Catalina关闭时,它(Manager)会将当前内存中的所有Session对象存储到一个文件中。当再次启动Catalina时,又会将这些Session 对象重新载入到内存中;
4)PersistentManagerBase类:该类会将 Session对象存储到辅助存储器中,其类图如下:(干货——PersistentManagerBase类会将 Session对象存储到辅助存储器中)

【2.1】Manager接口
1)intro to Manager interface:Session管理器是 Manager接口的实例;
2)Manager接口的源代码
public interface Manager {  public Container getContainer();  public void setContainer(Container container);  public DefaultContext getDefaultContext();  public void setDefaultContext(DefaultContext defaultContext);  public boolean getDistributable();   public void setDistributable(boolean distributable);  public String getInfo();   public int getMaxInactiveInterval();    public void setMaxInactiveInterval(int interval);   public void add(Session session);                                                                         public Session createEmptySession();  public void addPropertyChangeListener(PropertyChangeListener listener);    public Session createSession();  public Session findSession(String id) throws IOException;    public Session[] findSessions();   public void load() throws ClassNotFoundException, IOException;  public void remove(Session session);     public void removePropertyChangeListener(PropertyChangeListener listener);    public void unload() throws IOException;
}
对上述代码的分析(Analysis):
A1)getContainer()方法和setContainer()方法:以便将一个Manager实现与一个 Context容器相关联;
A2)createSession()方法:用来创建一个Session实例;
A3)add()方法:会将一个 Session实例添加到 Session池中;
A4)remove()方法:则会将一个 Session实例从 Session池中移除;
A5)getMaxInactiveInterval()方法和 setMaxInactiveInterval()方法:用来获取或设置一个时间长度,单位为秒;
A6)Session管理器: 会以此作为一个Session对象的最长存活时间;
A7)load()方法和unload() 方法:用来将Session对象持久化到辅助存储器中(干货——load方法和unload方法)
A7.1)load方法:该方法会将介质中的Session对象重新载入到内存中;
A7.2)unload方法:该方法会将当前活动的 Session对象存储到 Manager 实现指定的介质中;
【2.2】ManagerBase类(org.apache.catalina.session.ManagerBase)
1)ManagerBase.createSession()方法:该方法会创建一个新的Session对象, 每个Session对象都有一个 唯一的标识符;
2)generateSessionId()方法:可以通过 ManagerBase类受保护的方法generateSessionId() 方法来返回一个唯一的标识符;
Attention)一个活动的Session对象:指的是有效的,还未过期的Session对象;(干货——活动的Session对象的定义)
3)某个Context容器的 Session管理器会管理该 Context容器中所有活动的Session对象。这些活动的Session对象都存储在一个名为sessions 的 HashMap变量中:
protected HashMap sessions = new HashMap();
4)add方法,remove方法,findSession方法
 public void add(Session session) {synchronized (sessions) {sessions.put(session.getId(), session);if( sessions.size() > maxActive ) {maxActive=sessions.size();}}}
public void remove(Session session) {synchronized (sessions) {sessions.remove(session.getId());}}
public Session[] findSessions() {Session results[] = null;synchronized (sessions) {results = new Session[sessions.size()];results = (Session[]) sessions.values().toArray(results);}return (results);}
public Session findSession(String id) throws IOException {if (id == null)return (null);synchronized (sessions) {Session session = (Session) sessions.get(id);return (session);}}
【2.3】StandardManger类
1)intro to StandardManager:StandardManager类是Manager接口的标准实现,该类将 Session 存储于内存中;
1.1)该类还实现了Lifecycle接口:这样就可以由与其关联的Context容器来启动和关闭;其中stop方法的实现会调用 unload() 方法,以便将有效的Session对象序列化到一个名为"SESSION.ser"的文件中,而且每个Context容器都会产生这样一个文件;(干货——引入了SESSION.ser文件,unload() 方法持久化Session对象到文件
1.2)SESSION.ser文件:位于环境变量 CATALINA_HOME指定的目录下的work目录中;
看个荔枝)在tomcat4或tomcat5中,如果运行了实例应用程序,就可以在 CATALINA_HOME/work/Standalone/localhost/examples 目录下找到 SESSION.ser文件;
1.3)当StandardManager实例再次启动时:这些Session 对象会通过调用load() 方法重新读入内存中;(干货——load()方法将持久化文件中的Session对象重新读入内存)
2)Session管理器:还要负责销毁那些已经失效的Session对象,在tomcat4中的 StandardManager类中,这项工作是由一个专门的线程来完成的;
 public void run() { //org.apache.catalina.session.StandardManager// Loop until the termination semaphore is setwhile (!threadDone) {threadSleep();processExpires();}}
对上述代码的分析(Analysis):
A1)threadSleep()方法:会使线程休眠一段时间,长度由checkInterval指定,default==60 秒;
A2)processExpir()方法:会遍历由 Session管理器管理的所有Session对象,将Session实例的lastAccessedTime属性值与当前时间进行比较,如果两者之间的差值超过了变量 maxInactiveInterval 指定的数值,则会调用 Session接口的 expire() 方法使这个Session实例过期;
private void processExpires() { // org.apache.catalina.session.StandardManager.processExpires()long timeNow = System.currentTimeMillis();Session sessions[] = findSessions();for (int i = 0; i < sessions.length; i++) {StandardSession session = (StandardSession) sessions[i];if (!session.isValid())continue;int maxInactiveInterval = session.getMaxInactiveInterval();if (maxInactiveInterval < 0)continue;int timeIdle = // Truncate, do not round up(int) ((timeNow - session.getLastUsedTime()) / 1000L);if (timeIdle >= maxInactiveInterval) {try {expiredSessions++;session.expire();// highlight line.} catch (Throwable t) {log(sm.getString("standardManager.expireException"), t);}}}}
Attention)
A1)在tomcat5中,StandardManager类已不再实现 java.lang.Runnable接口。其中的 backgroundporocess() 方法会直接调用 tomcat5 中StandardManager对象的 processExpires()方法;
public voiid backgroundProcess() { //org.apache.catalina.session.StandardManager.backgroundProcess()processExpire();
}
public void expire() { //org.apache.catalina.sessoin.StandardManager.expire()expire(true);}   public void expire(boolean notify) { // the same as the above.// Mark this session as "being expired" if neededif (expiring)return;expiring = true;setValid(false);// Remove this session from our manager's active sessionsif (manager != null)manager.remove(this);// Unbind any objects associated with this sessionString keys[] = keys();for (int i = 0; i < keys.length; i++)removeAttribute(keys[i], notify);// Notify interested session event listenersif (notify) {fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);}// Notify interested application event listeners// FIXME - Assumes we call listeners in reverse orderContext context = (Context) manager.getContainer();Object listeners[] = context.getApplicationListeners();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 {fireContainerEvent(context,"beforeSessionDestroyed",listener);listener.sessionDestroyed(event);fireContainerEvent(context,"afterSessionDestroyed",listener);} catch (Throwable t) {try {fireContainerEvent(context,"afterSessionDestroyed",listener);} catch (Exception e) {;}// FIXME - should we do anything besides log these?log(sm.getString("standardSession.sessionEvent"), t);}}}// We have completed expire of this sessionexpiring = false;if ((manager != null) && (manager instanceof ManagerBase)) {recycle();}}
【2.4】PersistentManagerBase类(参见上图)
1)intro to PersistentManagerBase:该类是所有持久化Session管理器的 父类;
1.1)StandardManager实例 和 持久化Session管理器(PersistentManagerBase)的区别:在于后者中存储器的表示形式,即存储Session 对象的辅助存储器的形式,使用Store对象来表示;
2)在持久化Session管理器中:Session对象可以备份,也可以换出;(干货——Session对象可以备份,也可以换出);如果server destroyed,就可以从存储器中获取活动的Session对象;
2.1)当Session对象被换出:他会被移动到存储器中,因为当前活动的Session对象数超过了上限值,或者这个Session对象闲置了过长时间;(干货——换出是为了节省内存空间)
2.2)tomcat4中,PersistentManagerBase类实现了java.lang.Runnable 接口:使用一个专门的线程来执行备份和换出活动Session对象的任务;(run() 方法如下 )(干货——注意是tomcat4,使用一个专门的线程来执行备份和换出活动Session对象的任务)
public void run() { //org.apache.catalina.session.PersistentManagerBase.run()while(!threadDone) {threadSleep();processExpires(); // 检查Session对象是否过期.processPersistenceChecks();}    
}
public void processPersistenceChecks(){ // org.apache.catalina.session.PersistentManagerBase.processPersistenceChecks()processMaxIdleSwaps(); // highlight line.(下面我们只以processMaxIdleSwaps方法为例进行调用过程的分析,其他两个方法类似)processMaxActiveSwaps(); // highlight line.processMaxIdleBackups(); // highlight line.
}
/*** Swap idle sessions out to Store if they are idle too long.若session空闲太久则换出*/protected void processMaxIdleSwaps() {if (!isStarted() || maxIdleSwap < 0)return;Session sessions[] = findSessions();long timeNow = System.currentTimeMillis();// Swap out all sessions idle longer than maxIdleSwap// FIXME: What's preventing us from mangling a session during// a request?if (maxIdleSwap >= 0) {for (int i = 0; i < sessions.length; i++) {StandardSession session = (StandardSession) sessions[i];if (!session.isValid())continue;long lastAccessed = ((StandardSession)session).getLastUsedTime();int timeIdle = // Truncate, do not round up(int) ((timeNow - lastAccessed) / 1000L);if (timeIdle > maxIdleSwap && timeIdle > minIdleSwap) {if (debug > 1)log(sm.getString("persistentManager.swapMaxIdle",session.getId(), new Integer(timeIdle)));try {swapOut(session); // highlight line.} catch (IOException e) {;   // This is logged in writeSession()}}}}}
protected void swapOut(Session session) throws IOException { //换出操作if (store == null ||!session.isValid() ||isSessionStale(session, System.currentTimeMillis()))return;((StandardSession)session).passivate();writeSession(session); // highlight line.super.remove(session);session.recycle();}
protected void writeSession(Session session) throws IOException {if (store == null ||!session.isValid() ||isSessionStale(session, System.currentTimeMillis()))return;try {store.save(session); //highlight line.sessionSwapIgnore.remove(session.getId());} catch (IOException e) {log(sm.getString("persistentManager.serializeError", session.getId(), e));throw e;}}

3)tomcat5中,PersistentManagerBase类不再实现 java.lang.Runnable 接口。备份和换出Session对象的任务由其 backgroundProcess() 管理器完成,后者由其相关联的 StandardContext 实例周期性地调用;
(下面分析 换出和备份Session实例的实现过程)

4)PersistentManagerBase类换出Session实例
4.1)换出条件:只有当活动Session对象的数量超过了变量 maxActiveSessions指定的上限值,或者该Session对象闲置了很长时间后,才会被换出;(干货——Session对象被换出的condition)
4.1.1)当活动的Session实例数量过多时:PersistentManagerBase会将Session对象换出直到数量等于 maxActiveSessions;(参见 processMaxActiveSwaps()方法)
4.1.2)当某个Session对象闲置了过长时间:PersistentManagerBase会依据两个变量来决定是否将这个Session换出,这两个变量是minIdleSwap and maxIdleSwap;如果某个Session对象的 lastAccessedTime 属性的值超过了 minIdleSwap 和 maxIdleSwap 的值,就会将这个Session换出;(干货——为了防止换出Session对象,将maxIdleSwap设置为负数,参见 processMaxIdleSwaps() 方法)
4.2)如何查找Session实例:因为Session对象可以被换出,所以他既可能驻留在内存中,也可能驻留在存储器中,因此,findSession(String id ) 方法首先会在内存中查找是否存在该Session对象,若没有找到,才会在存储器中继续find。(PersistentManagerBase.findSession() 方法的源代码如下)(干货——Session对象被换出后,可能驻留在内存中,也可能驻留在存储器中)
public Session findSession(String id) throws IOException { //org.apache.catalina.session.PersistentManagerBase.findSession()Session session = super.findSession(id);if (session != null)return (session);// See if the Session is in the Storesession = swapIn(id); // highlight line.return (session);}
protected Session swapIn(String id) throws IOException { // the same as the above.if (store == null)return null;if (sessionSwapIgnore.contains(id)) {return null;}Session session = null;try {session = store.load(id); // highlight line.} catch (ClassNotFoundException e) {log(sm.getString("persistentManager.deserializeError", id, e));throw new IllegalStateException(sm.getString("persistentManager.deserializeError", id, e));}if (session == null) {sessionSwapIgnore.put(id,id);return (null);}if (!session.isValid()|| isSessionStale(session, System.currentTimeMillis())) {log("session swapped in is invalid or expired");session.expire(); // highlight line.store.remove(id); // highlight line.sessionSwapIgnore.put(id,id);return (null);}if(debug > 2)log(sm.getString("persistentManager.swapIn", id));session.setManager(this);// To make sure the listener knows about it.((StandardSession)session).tellNew();add(session);((StandardSession)session).activate();return (session);}

5)PersistentManagerBase类备份Session实例
5.1)备份条件:PersistentManagerBase实例 仅仅会备份那些空闲时间超过了变量 maxIdleBackup 的Session对象。
5.2)processMaxIdleBackups()方法:负责对该Session对象进行备份操作;

【2.5】PersistentManager类
1)intro to PersistentManager类:该类继承了 PersistentManagerBase类,仅仅是多添加了两个属性而已;
public final class PersistentManager extends PersistentManagerBase {  //org.apache.catalina.session.PersistentManager private static final String info = "PersistentManager/1.0";   protected static String name = "PersistentManager";  public String getInfo() {return (this.info);}  public String getName() {return (name);}}
【2.6】DistributedManager类(应用与集群环境)
1)intro to DistributedManager类:继承自PersistentManagerBase类,用于两个或多个节点的集群环境;
1.1)一个节点表示部署的一台 tomcat server:而集群中的节点 可以在同一台机器上,也可以在不同的machine上
1.2)每个节点必须使用 DistributedManager实例作为其Session管理器:这样才能支持复制 Session对象,这也是DistributedManager的主要功能 ;(干货——DistributedManager的主要功能)
2)DistributedManager会向其他节点发送消息:为了实现copy Session对象的目的,当创建或销毁Session对象时,DistributedManager会向其他节点发送消息;(干货——DistributedManager会向其他节点发送消息)
2.2)而且:集群中的节点也必须能够接受其他节点发送的消息。这样,http 请求才能到达集群中的任意节点;
3)catalina在org.apache.catlina.cluster中提供了一些工具:这些工具用于 与集群中其他节点的DistributedManager实例发送和接收消息;其中,ClusterSender 类用于向集群中的其他节点发送消息,ClusterReceiver 则用于接收集群中其他节点发送的消息;(干货——catalina在org.apache.catlina.cluster中提供了一些工具,如ClusterSender and ClusterReceiver)
4)DistributedManager.createSession()方法: 要创建一个Sesssion对象,存储在当前 DistributedManager 实例中,并使用 ClusterSender实例向其他节点发送消息;
 public Session createSession() {Session session = super.createSession(); // step1.ObjectOutputStream oos = null;ByteArrayOutputStream bos = null;ByteArrayInputStream bis = null;try {bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(new BufferedOutputStream(bos));((StandardSession)session).writeObjectData(oos);oos.close();byte[] obs = bos.toByteArray();clusterSender.send(obs); // step2.if(debug > 0)log("Replicating Session: "+session.getId());} catch (IOException e) {log("An error occurred when replicating Session: "+session.getId());}return (session);}
对以上代码的分析(Analysis):
step1)调用超类的 createSession()方法为自身创建一个 Session对象;
step2)使用ClusterSender实例,以字节数组的形式将该Session对象发送到集群中的其他节点;
5)DistributedManager类还实现了 java.lang.Runnable 接口: 这样就可以使用一个 专门的thread 来检查 Session对象是否过期,并从集群中的其他节点接收消息;(其run() 方法的源代码如下) 
public void run() { // org.apache.catalina.session.DistributedManager.run()// Loop until the termination semaphore is setwhile (!threadDone) {threadSleep();processClusterReceiver(); // 以该方法为例进行说明.processExpires();processPersistenceChecks();}}
/*** Called from our background thread to process new received Sessions**/public void processClusterReceiver() { //org.apache.catalina.session.DistributedManager.processClusterReceiver()Object[] objs = clusterReceiver.getObjects();StandardSession _session = null;ByteArrayInputStream bis = null;Loader loader = null;ClassLoader classLoader = null;ObjectInputStream ois = null;byte[] buf = new byte[5000];ReplicationWrapper repObj = null;for(int i=0; i < objs.length;i++) {try {bis = new ByteArrayInputStream(buf);repObj = (ReplicationWrapper)objs[i];buf = repObj.getDataStream();bis = new ByteArrayInputStream(buf, 0, buf.length);if (container != null)loader = container.getLoader();if (loader != null)classLoader = loader.getClassLoader();if (classLoader != null)ois = new CustomObjectInputStream(bis,classLoader);elseois = new ObjectInputStream(bis);_session = (StandardSession) super.createSession();_session.readObjectData(ois);_session.setManager(this);if (debug > 0)log("Loading replicated session: "+_session.getId());} catch (IOException e) {log("Error occurred when trying to read replicated session: "+e.toString());} catch (ClassNotFoundException e) {log("Error occurred when trying to read replicated session: "+e.toString());} finally {if (ois != null) {try {ois.close();bis = null;} catch (IOException e) {;}}}}}
【3】存储器(存储器是org.apache.catalina.Store接口的实例)
1)intro to 存储器是为Session管理器管理的 Session对象提供持久化存储器的一个组件;(干货——intro to 存储器——org.apache.catalina.Store接口的实例)
2)Store接口的源代码如下:
public interface Store { // org.apache.catalina.Storepublic String getInfo(); public Manager getManager(); public void setManager(Manager manager); public int getSize() throws IOException; public void addPropertyChangeListener(PropertyChangeListener listener); public String[] keys() throws IOException; public Session load(String id) throws ClassNotFoundException, IOException;public void remove(String id) throws IOException; public void clear() throws IOException; public void removePropertyChangeListener(PropertyChangeListener listener); public void save(Session session) throws IOException; 
} 
3)Store接口中有两个方法:save()方法和 load() 方法(干货——注意save方法和 load方法,它们的作用分别是什么)
3.1)save方法:用于将指定的 Session对象存储到某种持久性存储器中;
3.2)load方法:会从存储器中,依据Session对象 的标识符将该Session对象载入(加载)到内存中;
补充)keys()方法:会以字符串数组的形式返回所有 Session 对象的标识符;
4)其UML 类图如下所示:

【3.1】StoreBase类图(见上图)
1)intro to StoreBase:该类是一个抽象类,提供了一些基本功能,有两个子类;
2)StoreBase类:并没有实现Store接口的save()方法 和 load()方法,因为,这两个方法依赖于持久化Session对象的存储器的类型;
3)在tomcat4中:StoreBase类使用另一个线程周期性地检查 Session对象:从活动的Session的集合中移除过期的Session对象;(下面是 tomcat4 中 StoreBase.run()方法的源代码,注意是tomcat4
 public void run() { // org.apache.catalina.session.StoreBase.run()// Loop until the termination semaphore is setwhile (!threadDone) {threadSleep();processExpires();}}
对以上代码的分析(Analysis):
A1)processExpires方法:会获取所有活动的 Session对象,检查 每个Session对象的 lastAccessedTime属性值,删除那些长时间不活动的Session对象;源代码如下:(干货——再次提及了processExpires方法,注意其功能)
protected void processExpires() {// org.apache.catalina.session.StoreBase.processExpires()long timeNow = System.currentTimeMillis();String[] keys = null;if(!started) {return;}try {keys = keys();} catch (IOException e) {log (e.toString());e.printStackTrace();return;}for (int i = 0; i < keys.length; i++) {try {StandardSession session = (StandardSession) load(keys[i]);if (session == null) {continue;}if (!session.isValid()) {continue;}int maxInactiveInterval = session.getMaxInactiveInterval();if (maxInactiveInterval < 0) {continue;}int timeIdle = // Truncate, do not round up(int) ((timeNow - session.getLastUsedTime()) / 1000L);if (timeIdle >= maxInactiveInterval) {if ( ( (PersistentManagerBase) manager).isLoaded( keys[i] )) {// recycle old backup sessionsession.recycle();} else {// expire swapped out sessionsession.expire();}remove(session.getId());}} catch (Exception e) {log ("Session: "+keys[i]+"; "+e.toString());try {remove(keys[i]);} catch (IOException e2) {log (e2.toString());e2.printStackTrace();}}}}

Attention)在tomcat5中,不再使用专用的线程调用 processExpire()方法 ,而是,相关联的 PersistentManagerBase实例的 backgroundProcess()方法会周期性地调用 processExpires() 方法;

【3.2】FileStore类
1)intro to FileStore:FileStore类会将 Session对象存储到某个文件中,文件名会使用Session对象的 标识符再加上一个后缀 ".session" 构成;(干货——FileStore类的作用)
2)文件位于临时的工作dir下:可以调用FileStore.setDirectory() 方法进行修改临时目录的位置;
(干货——理清FileStore.save and FileStore.load方法的作用)
3)save方法:使用 java.io.ObjectOutputStream类将 Session对象进行序列化,故所有存储在Session实例中的对象都要实现 java.lang.Serializable接口;
4)load方法:该方法使用 java.io.ObjectInputStream 类实现 Session 对象的反序列化;

【3.3】JDBCStore类
1)intro to JDBCStore:该类将Session对象通过JDBC 存入 db中;(干货——JDBCStore类的作用)

【4】app program
1)intro to app program该应用程序中的Context容器使用StandardManager实例来管理Session对象,为了测试该应用程序,可以使用第3个示例servlet:SessionServlet;
public class SessionServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {System.out.println("SessionServlet -- service");response.setContentType("text/html");PrintWriter out = response.getWriter();out.println("<html>");out.println("<head><title>SessionServlet</title></head>");out.println("<body>");String value = request.getParameter("value");HttpSession session = request.getSession(true);out.println("<br>the previous value is " +(String) session.getAttribute("value"));out.println("<br>the current value is " + value);session.setAttribute("value", value);out.println("<br><hr>");out.println("<form>");out.println("New Value: <input name=value>");out.println("<input type=submit>");out.println("</form>");out.println("</body>");out.println("</html>");}
} 

【4.1】Bootstrap类
1)源代码如下
public static void main(String[] args) {//invoke: http://localhost:8080/myApp/SessionSystem.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector();Wrapper wrapper1 = new SimpleWrapper();wrapper1.setName("Session");wrapper1.setServletClass("SessionServlet");Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");context.addChild(wrapper1);// context.addServletMapping(pattern, name);// note that we must use /myApp/Session, not just /Session// because the /myApp section must be the same as the path, so the cookie will// be sent back.context.addServletMapping("/myApp/Session", "Session");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't startLifecycleListener listener = new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);// here is our loaderLoader loader = new WebappLoader();// associate the loader with the Contextcontext.setLoader(loader);connector.setContainer(context);// add a ManagerManager manager = new StandardManager(); // highlight line.context.setManager(manager); // highlight line.try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) context).start();// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}}
}
【4.2】SimpleWrapperValve类
1)servlet示例可以调用javax.servlet.http.HttpServletRequest接口的getSession() 方法获取Session对象:当调用 getSession() 方法时,request对象必须调用与Context 容器相关联的Session管理器。Session管理器组件要么创建一个新的session 对象,要么返回一个已经存在的session对象。request对象为了能够访问Session管理器,他必须能够访问Context容器。
2)为了达到这个目的:在 SimpleWrapperValve类的invoke方法中,需要调用 org.apache.catalina.Request接口的setContext()方法,并传入 Context实例;
Attention)SimpleWrapperValve.invoke方法会调用被请求的servlet实例的service方法,所以,必须在调用service方法之前设置 Context实例;
 public void invoke(Request request, Response response, ValveContext valveContext)throws IOException, ServletException {SimpleWrapper wrapper = (SimpleWrapper) getContainer();ServletRequest sreq = request.getRequest();ServletResponse sres = response.getResponse();Servlet servlet = null;HttpServletRequest hreq = null;if (sreq instanceof HttpServletRequest)hreq = (HttpServletRequest) sreq;HttpServletResponse hres = null;if (sres instanceof HttpServletResponse)hres = (HttpServletResponse) sres;//-- new addition -----------------------------------Context context = (Context) wrapper.getParent(); // highlight line.request.setContext(context); // highlight line.//-------------------------------------// Allocate a servlet instance to process this requesttry {servlet = wrapper.allocate();if (hres!=null && hreq!=null) {servlet.service(hreq, hres);}else {servlet.service(sreq, sres);}}catch (ServletException e) {}}
补充)org.apache.catalina.connector.HttpRequestBase类的私有方法:doGetSession() 会调用Context接口的 getManager() 方法来获取 Session管理器对象:(当获取了Session管理器对象之后,就可以获取到Session对象了,或直接创建一个新Session)
Manager manager = null;
if(contect != null)manager = context.getManager();

【4.3】打印结果
1)console info
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common.
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com/tomca
t/chapter9/startup/Bootstrap
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp
Starting Wrapper Session
StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom
StandardManager[/myApp]: Seeding of random number generator has been completed
SessionServlet -- service
SessionServlet -- service
SessionServlet -- service
SessionServlet -- service
2)访问信息



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

micrometer_具有InlfuxDB的Spring Boot和Micrometer第2部分:添加InfluxDB

micrometer自从我们添加了基本应用程序以来&#xff0c;是时候启动InfluxDB实例了。 我们将按照之前的教程进行操作&#xff0c;并添加一个docker实例。 docker run –rm -p 8086&#xff1a;8086 –name influxdb-本地influxdb 是时候在我们的pom上添加微米InfluxDB依赖项了…

漫画:什么是volatile关键字?(整合版)

转载自 永远爱大家的 程序员小灰 ————— 第二天 ————— ———————————— Java内存模型简称JMM&#xff08;Java Memory Model&#xff09;&#xff0c;是Java虚拟机所定义的一种抽象规范&#xff0c;用来屏蔽不同硬件和操作系统的内存访问差异&#xff0c;让j…

tomcat(supplement)HttpConnector.initialize() 和 start() 方法 以及 StandardContext.start()方法的分析

【0】README 0.0&#xff09;本文中源代码的背景&#xff0c;参见 tomcat(9)session管理 0.1&#xff09;本文主要以图片的形式分析他们大致的调用过程&#xff1b; 0.2&#xff09;HttpConnector org.apache.catalina.connector.http.HttpConnector; 而StandardContext o…

restful rest_HATEOAS的RESTful服务。 超媒体:REST的秘密要素

restful rest在这篇文章中&#xff0c;我们将介绍有关HATEOAS的RESTful服务的综合文章。 超媒体是REST的秘密成分。 1.简介 在本教程的前一部分中&#xff0c;我们花了一些时间来刷新有关REST体系结构样式的基本原理的知识。 业界对REST状态的批判性眼光揭示了一个令人失望的…

漫画:什么是单例设计模式

转载自 永远爱大家的 程序员小灰 ————— 第二天 ————— 单例模式第一版&#xff1a; 1234567891011public class Singleton {private Singleton() {} //私有构造函数private static Singleton instance null; //单例对象//静态工厂方法public static Singleton ge…

如何在工作繁重、睡眠较少的情况下保持旺盛精力?

作者&#xff1a;陈炬 链接&#xff1a;https://www.zhihu.com/question/23177623/answer/47785761 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 本人也在创业&#xff0c;结合《精力管理》一书&#xff0c;说说我…

mockito接口没法赋值_Mockito:无法实例化@InjectMocks字段:类型是接口

mockito接口没法赋值使用Mockito进行Java类的模拟和存根的任何人&#xff0c;可能都熟悉InjectMocks -annotation。 在要测试的类上使用此批注&#xff0c;Mockito将尝试通过构造函数注入&#xff0c;setter注入或属性注入来注入模拟。 魔术成功了&#xff0c;它无声地失败了&a…

tomcat(10)安全性

【0】README0.0&#xff09;本文部分描述转自“深入剖析tomcat”&#xff0c;旨在学习 tomcat(10)安全性 的基本知识&#xff1b;0.1&#xff09;servlet技术支持通过配置部署描述器&#xff08;web.xml&#xff09;文件来对这些内容进行访问控制&#xff1b;&#xff08;干货—…

SonarQube 8.3.x中的Maven项目的测试覆盖率报告

几年前&#xff0c;我写了一篇博客文章&#xff0c;介绍如何在SonarQube中生成测试报告&#xff0c;该报告独立于单元测试和集成测试的测试报告中。 从SonarQube 6.2开始&#xff0c;测试报告不再在这些类别中分开&#xff08;请参阅SonarQube的博客文章 &#xff09;。 SonarQ…

单例模式懒汉、饿汉和登记

转载自 JAVA设计模式之单例模式本文继续介绍23种设计模式系列之单例模式。 概念&#xff1a;  java中单例模式是一种常见的设计模式&#xff0c;单例模式的写法有好几种&#xff0c;这里主要介绍三种&#xff1a;懒汉式单例、饿汉式单例、登记式单例。  单例模式有以下特点…

量角器中Selenium定位器的完整指南(示例)

在测试网站的功能时&#xff0c;特别是Web元素&#xff08;例如单选按钮&#xff0c;文本框&#xff0c;下拉列表等&#xff09;&#xff0c;您需要确保能够访问这些元素。 Selenium定位器正是出于这个目的&#xff0c;通过使用此命令&#xff0c;我们可以识别这些Web元素DOM&a…

MySQL的自然联结+外部联结(左外连接,右外连接)+内部联结

【0】README0.1&#xff09;本文旨在review MySQL的自然联结外部联结&#xff08;左外连接&#xff0c;右外连接&#xff09;内部联结 的相关知识&#xff1b;【1】自然联结1&#xff09;自然联结定义&#xff1a;无论何时对表进行联结&#xff0c;应该至少有一个列出现不止一个…

MySQL 添加列+修改列+删除列

【0】REAMDE 0.1&#xff09;本文部分文字描述转自 http://blog.163.com/zhangjie_0303/blog/static/99082706201191911653778/ 0.2&#xff09;本文旨在review mysql 对列的相关操作&#xff1a;如添加&#xff0c;修改&#xff0c;删除以及重命名表名等操作&#xff1b; 【1】…

compose应用_带有PostgreSQLDocker Compose for Spring Boot应用程序

compose应用在此博客文章中&#xff0c;您将学习如何使用PostgreSQL配置Spring Boot应用程序以与Docker Compose一起运行。 这篇博客文章涵盖&#xff1a; Spring Boot应用程序Dockerfile配置&#xff0c;在依赖项和资源之间进行了清晰的分离 用于通过PostgreSQL运行应用程序…

单例模式面试题

转载自 单例模式面试题&#xff08;特点、理解&#xff09; (1)单例模式特点&#xff08;什么是单例模式&#xff09;&#xff1f;  a.单例类只能有一个实例。  b.单例类必须自己创建自己的唯一实例。  c.单例类必须给所有其他对象提供这一实例。 (2)单例模式的作用&#x…

MySQL的source命令不加分号和delimiter的使用

【0】README 0.1&#xff09;本文旨在 review source 命令&#xff0c; 这一直是我的痛&#xff0c;为什么一直导入 sql 文件不成功&#xff0c;一直没有写 blog 吧他 记录下来&#xff08;事实上&#xff0c;也间接证明我就是个小白&#xff09;&#xff1b; 0.2&#xff09…

selenium自动化测试_维持Selenium测试自动化的完美方法

selenium自动化测试毫无疑问&#xff0c; 自动浏览器测试已改变了软件开发的工作方式。 如果不是Selenium&#xff0c;我们将无法像我们一样使用各种各样的无错误的Web应用程序。 但是有时&#xff0c;甚至IT部门也误解了自动化一词。 大多数人认为计算机将为他们完成所有测试…

单例模式的优与劣

转载自 大话设计模式(四)单例模式的优与劣前言首先来明确一个问题&#xff0c;那就是在某些情况下&#xff0c;有些对象&#xff0c;我们只需要一个就可以了&#xff0c;比如&#xff0c;一台计算机上可以连好几个打印机&#xff0c;但是这个计算机上的打印程序只能有一个&…

MySQL存储过程+游标+触发器

【0】README0.1&#xff09;本文旨在 arrange mysql 存储过程及如何在存储中使用游标 的相关知识&#xff1b;0.2&#xff09;delimieter的用法&#xff1a;参见 http://blog.csdn.net/pacosonswjtu/article/details/51407756&#xff1b;【1】存储过程基础1&#xff09;intro…

java迭代器退出迭代_使用Java迭代器修改数据时要小心

java迭代器退出迭代随着本学期的结束&#xff0c;我想我会分享一个关于如何非常熟悉Java迭代器的小故事。 现实世界语境 就上下文而言&#xff0c;我开设了第二年软件组件课程&#xff0c;这是尝试进入该专业的学生的最后障碍。 当然&#xff0c;这门课程对学生来说压力很大&a…