How Tomcat works之第九章 会话管理

来源:互联网 发布:浙江省网络监管服务网 编辑:程序博客网 时间:2024/05/16 23:58

回顾

Catalina 通过一个叫做管理的组件支持会话管理,由org.apahce.catalian.Manager接口表示。一个管理者总是与上下文关联。其中,一个管理者负责创建,更新和销毁(使失效)会话对象,也为任何请求组件返回一个有效的会话对象。


一个Servlet可以通过调用javax.servlet.http.HttpServletRequest接口的getSession方法获取一个会话对象,其由org.apache.catalina.Connector.HttpReuquestBase类实现,作为默认的连接器。这里是这个类相关的一些方法。


public HttpSession getSession() {
   return (getSession(true));
}
public HttpSession getSession(boolean create) {
   ...
   return doGetSession(create);
}
private HttpSession doGetSession(boolean create) {
   // There cannot be a session if no context has been assigned yet
   if (context == null)
      return (null);
   // Return the current session if it exists and is valid
   if ((session != null) && !session.isValid())
      session = null;
   if (session != null)
      return (session.getSession());

   // Return the requested session if it exists and is valid
   Manager manager = null;
   if (context != null)
      manager = context.getManager();
   if (manager == null)
      return (null);           // Sessions are not supported
   if (requestedSessionId != null) {
      try {
         session = manager.findSession(requestedSessionId);
      }

catch (IOException e) {
          session = null;
      }
      if ((session != null) && !session.isValid())
          session = null;
      if (session != null) {
         return (session.getSession());
      }
   }

   // Create a new session if requested and the response is not
   // committed
   if (!create)
      return (null);
   ...
   session = manager.createSession();
   if (session != null)
      return (session.getSession());
   else
      return (null);
}


默认的,管理者在内存中存储了它的会话对象。然而,tomcat也允许管理者持续化会话对象到一个文件存储或者一个数据库(通过jdbc)。Catalina提供了org.apache.catalina.session包,包含了与会话对象和会话管理关联的类型。


这章演示了Catalina中会话管理,分成三个部分,“Sessions”,“Managers”,"Stores".最后一部分演示了一个与管理关联的上下文的应用程序。


会话

在Servlet编程中,会话对象由javax.servlet.http.HttpSession接口表示。这个接口的实现类是StandardSession类,在org.apache.catalina.session包中。可是,由于安全原因,管理者不传递一个StandardSession实例给一个Servlet。其使用一个门面类StandardSessionFacade类,其在org.apache.catalina.session包中。在其内部,manager与另一个门面一起工作:org.apache.catalian.session包。与session相关类型的UML图如9.1所示:


图9.1 session-related类型


session 接口


Session接口扮演Catalina-internal的门面。StandardSession类实现了Session,HttpSesion接口。代码如下:

package org.apache.catalina;
import java.io.IOException;
import java.security.Principal;
import java.util.Iterator;
import javax.servlet.ServletException;
import 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);
}
一个session对象总是包含于一个manager中,setManager和getManager方法用于与一个manager关联Session实例。一个session实例也有一个唯一标识使其上下文与其manaager关联。session标识通过setId和getId方法访问。getLastAccessedTime方法由管理者调用来确定session对象的有效性。管理者调用setValid方法来设置或者重置会话对象的有效。每次session实例被访问,它的访问方法就被调用来更新它的最后一次访问时间。最后,manager可以调用输出方法输出一个session对象并调用getSession方法返回一个由这个门面包装的HttpSesion对象。


StandardSession类

StandardSession类是Session接口的标准实现。除了实现了HttpSesion和Session外,还实现了序列化使Session对象能序列化。


此类的构造器接受一个Manager实例,使一个Session对象总是有一个Manager实例。


public StandardSession(Manager manager)


下面是维持状态的私有变量。注意关键字transient使一个变量非序列化。

// session attributes
private HashMap attributes = new HashMap();
// the authentication type used to authenticate our cached Principal,
if any
private transient String authType = null;
private long creationTime = 0L;
private transient boolean expiring = false;
private transient StandardSessionFacade facade = null;
private String id = null;
private long lastAccessedTime = creationTime;
// The session event listeners for this Session.

private transient ArrayList listeners = new ArrayList();
private Manager manager = null;
private int maxInactiveInterval = -1;
// Flag indicating whether this session is new or not.
private boolean isNew = false;
private boolean isValid = false;
private long thisAccessedTime = creationTime;

Note In Tomcat 5 the above variables are protected, in Tomcat 4 they are private.
      Each of these variables has an accessor and a mutator (the get/set methods).

The getSession method creates a StandardSessionFacade object by passing this
instance:

public HttpSession getSession() {
   if (facade == null)
      facade = new StandardSessionFacade(this);
   return (facade);
}

注意 在tomcat5中上述变量是protected ,在tomcat4中是privated。每个都有set和get方法。


getSession方法以传递这个Session实例创建StandardSessionFacade对象,

public HttpSession getSession() {
   if (facade == null)
      facade = new StandardSessionFacade(this);
   return (facade);
}

一个Session对象,如果超过maxInactiveInternal值后还没有被访问,将被舍弃。执行这个操作是的是Session接口的expire方法。这个方法的实现在tomcat4中如下:


public void expire(boolean notify) {
   // Mark this session as "being expired" if needed
   if (expiring)
      return;
   expiring = true;
   setValid(false);
// Remove this session from our manager's active sessions
if (manager != null)
   manager.remove(this);
// Unbind any objects associated with this session
String keys [] = keys();
for (int i = 0; i < keys.length; i++)
   removeAttribute(keys[i], notify);
// Notify interested session event listeners
if (notify) {
   fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
}

// Notify interested application event listeners
// FIXME - Assumes we call listeners in reverse order
Context 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 session
   expiring = false;
   if ((manager != null) && (manager instanceof ManagerBase)) {
      recycle();
   }
}

上述方法中执行了使一组内部变量失效,从管理者中移除session对象并触发一些事件。


StandardSessionFacade类


为了传递一个session对象给一个Servlet,Catalina要实例化StandardSession类,植入赋值给它之后传递给servlet。可是,它传递一个StandardSessionFacade类实例,这个类仅仅实现了HttpSession类的方法。这样,servlet开发人员就不能获取HttpSesion对象,也不能访问不想开放的public方法。


管理者

其管理会话对象。例如,它创建并销毁它们。管理者由org.apache.catalina.Manager接口表示。在Catalina中,org.apache.catalina.session包包含了ManagerBase类,提供了普通功能的实现。这个类有两个直接的子类,StandardManager和PersistentManagerBase。


当运行的时候,StandardManager在内存中存储session对象。当停止的时候,它保存内存中当前所有session对象到一个文件中。当再次启动的时候,它加载回这些session对象。


PersistentManagerBase是管理者组件的一个基类,其存储session对象作为第二存储。其有两个子类:PersistentManager和DistributedManager(其在tomcat 4中有效)。Manager接口及其实现类的UML图如下:



Manager 接口


其代表了一个Manager组件


Listing 9.3: The Manager interface

package org.apache.catalina;
import java.beans.PropertyChangeListener;
import java.io.IOException;

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 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;
}

首先,Manager接口有一个setContainer方法和getContainer方法,使管理者实现类与一个context关联。createSession方法创建一个session对象。add方法增加一个session实例到session池中,remove方法从session池中移除session对象。getMaxInactiveInerval方法和setMaxInactiveInterval方法返回和指定秒数,其为Manager在使session失效前将等待一个使用者关联一个session对象的时间。


最后,这里的load和unload方法支持持续化session到一个Manager实现(其支持一种持续化机制)的第二存储中。unload方法将当前活动会话保存在Manager实现的一个存储中,load方法将session对象加载回内存。


ManagerBase类


ManagerBase是一个抽象类,所有的Manager类都要继承它。这个类为其子类提供了通用的功能。尤其是,这个类提供了createSession方法来创建一个session对象。每个session有一个唯一标示。其protected方法generateSessionId返回一个唯一标示。


注意:一个活动session是一个有效会话对象(没有死亡)。


对于一个给定的上下文,管理者实例管理者这个上下文中的所有活动会话。这些活动会话存储在名叫sessions的HashMap中。


protected HashMap sessions = new HashMap();


add方法将session对象添加到HashMap中。代码如下:


public void add(Session session) {
      synchronized (sessions) {

 sessions.put(session.getId(), session);
       }
   }


remove方法从sessions的HashMap中移除。这里是一个remove方法


public void remove(Session session) {
       synchronized (sessions) {
          sessions.remove(session.getId());
       }
   }


无参的findSession方法返回sessions的HashMap中的所有活动会话,以一个Session实例为元素的数组返回。带参的findSession方法 其参数为Session标识,返回标识指定的session 实例。


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);
   }
}



StandardManager 类


其为ManagerBase类的子类并在内存中存储所有活动会话。它实现了生命周期接口这样它能开始和停止。stop方法的实现调用了unload方法,其为每个上下文序列化了有效的session实例到一个文件,叫做SESSIONS.ser。这个文件在CATALIAN_HOME目录下的工作目录下。当StandardManager类再次启动后,这些session对象被load方法重新加载回内存中。


manager也负责销毁不再有效地session对象。在Tomcat 4 的StandardManger中,调用一个指定的线程完成。因为这个原因,StandardManager实现了java.lang.Runnable。其run方法的代码如下:



sleepThreads方法使线程休眠指定的时间,由checkInterval变量指定,默认为60秒。setCheckInterval方法改变。


processExpire方法循环遍历管理者管理的所有活动线程,并获取每个会话的最后访问时间与当前时间比较。如果两者的差值超出了maxInactiveInterval,这个方法就调用Session接口的expire方法来销毁这个Session实例。调用setMaxInacticeInterval方法设置maxInactiveInterval的值。其默认值也是60.不要愚蠢的认为这是一个用在tomcat 部署的值。setContainer方法,被org.apache.catalian.core.ContainerBase类的setManager方法(调用它使一个manager对象与context关联),重写这个值。下面是个processExpire方法的代码片段。


注意:sessionTimeOut变量的默认值在org.apache.catalina.core.StandardContext类中是30秒


在Tomcat 5 中,StandardManager类不实现java.lang.Runnable。在Tomcat 5中的StandardManager对象的processExpire方法直接被后台处理方法调用,这在Tomcat 4中是不可以的。



StandardManager类的backgroundProcess方法被org.apache.catalina.core.StandardContext实例的backgroundProcess方法调用,容器使上下文与manager关联。StandContext类周期性的调用backgroundProcess方法,将在第十二章中讨论。


PersistentManager类


其为所有持续管理者的父类。在一个StandardManager和一个持续性的Manager之间的主要区别是后者存储的形式。PersistentManagerBase类使用一个名叫store的私有对象引用。


private Store store  = null;

在一个持续性的管理类中,会话对象既可以备份也可以换出。当一个会话对象备份的时候,那个会话对象就会被复制进一个store中,然后原始数据停留在内存中。如果服务崩溃了,活动会话对象能从store中重新获取。当一个会话对象被换出的时候,它移到store 中,因为活动会话对象的数量超出了指定的数量或者会话对象已经空闲很久了。换出的意图是节省内存。


在Tomcat 4中PersistentManagerBase实现了java.lang.Runnable,安排一个独立的线程例行地备份和换出活动线程。下面是代码片段:




processExpire方法就像StandardManager中的一样,检查销毁会话对象。processPersistentChecks方法调用三个其他方法:



在Tomcat 5中PersistentManager类不实现java.lang.Runnable。备份和换出由它的backgroundProcess管理者完成,也是由其相关联的StandardContext实例周期性的调用。


换出和备份将在下面部分讨论。


Swap out  换出


PersistentManager类定义了大量的规则来换出Session对象。一个Session对象被换出要不是因为超过了最大活动session的数量,要不就是空闲太久了。


在这种情况下有许多session对象,PersistentManagerBase实例仅换出一些session对象,直到活动session对象与maxInactiveInterval值相等。


如果是session对象空闲时间太久的情况下,PersistentManagerBase提供两个变量来决定这个session对象是否应该被换出:minIdleSwap和maxIdleSwap。如果一个会话对象的最后一次访问时间超出了minidleSwap和maxIdleSwap时间。为阻止换出,你可以设置maxIdleSwap为无穷大。


因为一个活动会话能够被换出,它能放在内存中或者在store中。因此,findSession(String id)方法首先在内存中查找会话实例,如果没有找到,就到store中查找。下面这个方法的实现:




Back-up 备份


不是所有活动会话要备份。PersistentManagerBase实例仅仅备份会话对象,已经空闲很久并超出maxIdleSwap值。processMaxIdleBackups方法执行会话对象备份。


PersistentManager


此类继承于PersistentManagerBase类,它只有两个属性。


DistributedManager


Tomcat 4提供了此类。PersistentManagerBase类的子类,DistributedManager类用于两个或者多个节点的集群环境。一个节点代表一个Tomcat部署。集群中的节点可以存在于不同的机器或者相同的机器上。在一个集群环境中,节点必须使用DistributedManager类的一个实例作为节点的管理者来支持会话响应,这是DistributedManager的主要责任。


为达到响应目的,DistributedManager发送通知给其他的节点,无论是会话对象被创建或者是销毁。另外,一个节点必须也能够从其他节点接受消息。这样,一个HTTP请求可以在集群上的任何节点提供服务。


为发送和接受消息,从其他节点的DistributedManager的其他实例上,Catalina在org.apache.catalina.cluster包的中提供了相关类。尤其是,ClusterSender类用于发送通知给其他节点和ClusterReceiver类用于从其他节点接受通知。


DistributedManager的createSession方法必须创建一个会话对象来存储当前实例并使用clusterSender实例发送消息给其他节点。下面是代码片段:


首先,createSession方法调用其父类的createSession方法为其自身创建一个Session对象,之后使用ClusterSender以字节数组发送这个会话对象。


DistributedManager也实现了java.lang.Runnable,有一个独立线程来销毁会话对象和接受来自其他节点的通知。run方法代码如下:

public void run() {
   // Loop until the termination semaphore is set
   while (!threadDone) {
      threadSleep();
      processClusterReceiver();
      processExpires();
      processPersistenceChecks();
   }

}


这个方法值得注意的是调用processClusterReceiver方法,其处理来自其他节点的会话创建通知。


Stores


Store,由org.apache.catalian.Store.catalian接口负责,是一个为Manager管理的会话提供持久性存储的组件。接口代码如下:


public interface Store {
   public 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;
   pubiic void removePropertyChangeListener(PropertyChangeListener

      listener);
   public void save(Session session) throws IOException;
}


接口中最重要的两个方法是load和save方法。save方法将一个指定的Session对象存入到一个持久性存储中。load方法从存储中加载一个给定Session标识的Session对象。keys方法返回一个包含所有会话标识的字符串数组。


Store接口和它的实现类的UML图如下:




图9.3 Store接口及其实现类


以下部分讨论了StoreBase,FIleStore和JDBCStore类


StoreBase

StreoBase是一个抽象类,为其两个子类提供了通用方法。此类没有实现Store接口的save和load方法:因为这些方法的实现取决于需持续化会话到存储的类型。


Tomcat 4的StoreBase类使用一个独立线程周期性的检查失效会话并从活动会话的集合中移除失效会话。下面是run方法代码:


public void run() {
   // Loop until the termination semaphore is set
   while (!threadDone) {
      threadSleep();
      processExpires();
   }
}


processExpores()方法移除所有活动会话并检查每个会话的最后访问时间,并移除空闲很久的会话对象。



protected void 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.isValid())
             continue;
int maxInactiveInterval = session.getMaxInactiveInterval();
          if (maxInactiveInterval < 0)
             continue;
          int timeIdle = // Truncate, do not round up
             (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
          if (timeIdle >= maxInactiveInterval) {
             if ( ( (PersistentManagerBase) manager).isLoaded( keys[i] )) {
                 // recycle old backup session
                 session.recycle();
             }
             else {
                 // expire swapped out session
                 session.expire();
             }
             remove(session.getId());
          }
       }
       catch (IOException e) {
          log (e.toString());
          e.printStackTrace();

       }
       catch (ClassNotFoundException e) {
          log (e.toString());
          e.printStackTrace();
       }
   }
}


在Tomcat 5中没有指定线程来执行processExpire方法。这个方法由相关联的PersistentManagerBase实例的backgroundProcess方法周期性调用。


FileStore

FIleStore类在一个文件中存储会话对象。文件以会话对象的标示符加上表达式.session命名。这个文件存放于临时工作目录,通过调用FileStore类的setFileStore方法可以改变临时目录。


java.io.ObjectOutputStream类用于save方法来序列化会话对象。所以,存储于会话实例的所有对象必须实现java.lang.Serializable。为了在load方法中饭序列化会话对象,就要使用java.io.ObjectInputStream类了。


JDBCStore

此类在数据库中存储会话对象并通过JDBC传输。因此,为了使用JDBCStore,需要提供driver名和连接URL,分别调用setDriverName和setConnectionURL即可。






0 0
原创粉丝点击