How tomcat works——9 session管理

概述

Catalina通过名为manager的组件来完成session管理,该组件由org.apache.catalina.Manager接口表示。 一个manager总是与context相关联。 其中,manager负责创建,更新和销毁(无效)session对象以及可返回有效的session对象给任何请求组件。

一个 servlet 可以使用 getSession()方法获得一个 session 对象,该方法在javax.servlet.http.HttpServletRequest 中定义。它在默认连接器里由org.apache.catalina.connector.HttpRequestBase 类实现。这里是HttpRequestBase 类的一些相关方法。

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

默认情况下manager将 session 对象存储在内存中,但是 Tomcat 也允许将 session对象存储在文件或者数据库中(通过 JDBC)。Catalina 在org.apache.catalina.session 包中提供了 session 对象和 session 管理的相关类型。

本章使用了3节解释了Catalina中的 session 管理:Session、Managers 和 Stores。最后一节使用context容器以及相关联的manager介绍演示了一应用Demo。

9.1 Sessions

在 servlet 编程中,一个 session 对象使用 javax.servlet.http.HttpSession接口表示。该接口的标准实现是org.apache.catalina.session 包中的StandardSession 类。但是出于安全考虑,manager并不会将一个 StandardSession 实例传递给 servlet。而是使用org.apache.catalina.session 包中的外观类 StandardSessionFacade类。在内部,一个manager使用了另一个外观:org.apache.catalina.Session 接口。Session 相关类型的 UML 结构图如图 9.1。注意,出于简便考虑,已将 Session,StandardSession和 StandardSessionFacade 前缀org.apache.catalina省略。
这里写图片描述
图9.1: Session-related types

9.1.1 Session接口

Session接口扮演着一 个 Catalina 内部外观角色 。它的标准实现StandardSession 也实现了 javax.servlet.http.HttpSession 接口。Session接口如 Listing9.1 所示:

Listing 9.1: The Session interface

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() 方法来关联一个 Session 对象和一个管理器。另外,一个 Session实例在跟管理器相关联的上下文中(context)有一唯一标识ID。对于该 ID 有 setId() 和 getId() 方
法来设置和访问。管理器通过调用getLastAccessedTime()方法来判断一个 Session 对象是否合法。管理器调用 setValid() 方法来设置或重置一个 session 的合法性。每次一个Session 被访问时,access()方法都会被调用,以此来更新它的最后访问时间。最后,管理器可以调用 expire() 方法来终止一个会话,可以使用 getSession()方法获得一个包装在该外观内的 HttpSession 对象。

9.1.2 StandardSession类

StandardSession 是 catalina 中 Session 接口的标准实现。除了实现 javax.servlet.http.HttpSession和org.apache.catalina.Session 接口外,StandardSession 类还实现了 java.lang.Serializable 接口,使session对象可序列化。

StandardSession 的构造函数接收一个 Manager 类实例,迫使一个 session 实例必须属于一个 manager:

public StandardSession(Manager manager);

下面是 StandardSession 实例的一些比较重要的变量,用于保存该实例的一些状态。注意,带有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;

注意:在tomcat4中,上面的变量是private的,在tomcat5 中,访问修饰符已经改为 protected。每个变量都有设置和访问的方法(set()/get()方法)。

getSession() 方法会依据当前实例创建返回一个 StandardSessionFacade 类的实例:

public HttpSession getSession() {
    if (facade == null)
        facade = new StandardSessionFacade(this);  //传入该session 实例
    return (facade);
}

当某个 session 对象在超过一定时间没被访问后,会通过调用 Session 接口的 expire() 方法将该 session 对象标识为过期,这个时间是由变量maxInactiveInterval 表示。该方法在Tomcat4中的 StandardSession内实现如Listing 9.2:

Listing 9.2: The expire method

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

    }

在 Listing9.2 的过程中包括设置内部变量expiring、从管理器中删除Session 对象、并触发一些事件。

9.1.3 StandardSessionFacade类

为了传递一个 session 对象给 servlet,catalina 可以实例化一个 StandardSession对象,填充 其内容,然后再传给servlet。但是,实际上,catalina 传递的是session 的外观类 StandardSessionFacade 的实例,该类仅仅实现了 javax.servlet.http.HttpSession 接口。这样,servlet 开发人员就不能将 HttpSession 对象向下转换为StandardSessionFacade 类型,也就不会访问到那些未向程序员暴露的公共方法了。

9.2 Manager

管理器(Manager)组件负责管理 session 对象。例如创建和销毁 session 对象。Catalina 中 manager 组件由org.apache.catalina.Manager 接口表示。org.apache.catalina.session 包内的 ManagerBase 是 Manager 接口的基本实现,它有2个子类:StandardManager 和 PersistentManagerBase 类。

当运行 tomcat 时,StandardManager 实例负责在内存中管理 session,但当服务器关闭时,会将当前内存中的session 写入到文件中,等服务器再次启动时,会重新载入这些 session对象。

PersistentManagerBase 类是 manager 组件的基类,将 session 对象存储在二级存储设备中。它有2个直接子类:PersistentManager 和 DistributedManager 类(DistributedManager 类仅存在于 tomcat4 中)。相关类的UML结构图如图 9.2 所示:

这里写图片描述
图9.2: The Manager interface and its implementations

9.2.1 Manager接口

Manager接口代表着一 Manager组件。它的定义如Listing 9.3。

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接口具有getContainer()和setContainer()方法来将Manager实现与上下文(context)相关联。 createSession()方法负责创建一个Session对象; add()方法负责向会话池添加一个Session实例;remove()方法从池中删除一Session对象; getMaxInactiveInterval()和setMaxInactiveInterval()方法用于获取和设置会话的最长有效时间秒数。

最后,unload()方法可以将 session 对象持久化到二级存储设备中,load()方法则可以将其载入到内存中。

9.2.2 ManagerBase类

ManagerBase 类是一个抽象类,派生实现了 Manager 接口。该类为子类提供了一些基本的功能。其中,createSession() 方法会创建一个新的 session 对象。另protected修饰的generateSessionId() 方法会返回一个 session的唯一标识符。

注意:活动会话是指仍然有效的会话对象(尚未过期)。

拥有上下文(Context)的Manager实例管理上下文中的所有活动会话。这些活动session 对象存储在一个名为sessions 的 HashMap 类型的变量中:

protected HashMap sessions = new HashMap();

下面是add() 和 remove()方法的实现:

public void add(Session session) {
    synchronized (sessions) {
        sessions.put(session.getId(), session);
    }
}

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

无参findSessions()方法以数组形式返回所有活动的 session 对象;findSession() 方法则返回某个指定id 的 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);
    }
}

9.2.3 StandardManager类

StandardManager类是Manager接口的标准实现,该类将 session 对象存储在内存中。它还实现了Lifecycle接口(见第6章 “生命周期”),以便于可由 container 负责启动/关闭。其中,stop() 方法会调用 unload() 方法将 session 对象序列化到一个名为 Session.ser 的文件中,每个 context 一个文件。SESSIONS.ser 文件位于 CATALINA_HOME目录下的work 目录中。例如,在Tomcat4和Tomcat5中,我们运行例子时,可以在CATALINA_HOME/work/Standalone/localhost/examples下发现SESSIONS.ser文件。当 StandardManager再次启动时,会调用 load() 方法从文件中读取session 对象到内存中。

当session 无效时,manager 组件要负责销毁 session 对象。在 tomcat4中,StandardManager类使用专一线程完成此任务。因此,StandardManager 类还实现了 java.lang.Runnable 接口。Listing 9.4展现了Tomcat 4中StandardManager的run()方法实现:

Listing 9.4: The run method of StandardManager in Tomcat 4

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

threadSleep()方法会使线程休息一段时间,时间长度由变量 checkInterval 指定,单位为秒,默认为 60秒,可通过setCheckInterval() 方法进行设置。

processExpires()方法循环 manager 管 理 的 所 有 的 session , 比 较 当 前 时 间 与 session 对 象 的lastAccessedTime 属性值。若两个时间差值大于maxInactiveInterval,则调用session 对象的 expire()方法将其标识为过期的。变量 maxInactiveInternal 的值可通过setMaxInactiveInterval()方法进行设置,时间单位是秒。变量maxInactiveInterval在StandardManager中默认值是60。但,不要傻乎乎认为 tomcat 部署时使用的值。org.apache.catalina.core.ContainerBase 类的 setManager ()方法会调用setContainer()方法(我们通常会调用setManager()方法使manager和context关联),而setContainer() 方法会修改 maxInactiveInterval 的值。代码片段如下:

setMaxInactiveInterval(((Context)this.container).getSessionTimeout()*60 );

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

在 tomcat5 中,StandardManager 类没有实现 java.lang.Runnable 接口,processExpires()方法会直接被backgroundProcess()方法调用,tomcat4 中并不包含 backgroundProcess 方法:

public void backgroundProcess() {
    processExpires();
}

StandardManager的 backgroundProcess()方法会被 org.apache.catalina.core.StandardContext 实例的backgroundProcess()方法调用,容器关联着此manager。而StandardContext 类会周期性的调用 backgroundProcess ()方法,这将在第12章讨论。

9.2.4 PersistentManagerBase类

PersistentManagerBase 类是所有持久化 manager 的父类。StandardManager 类和持久化 manager 的区别在于后者的存储方式(放store中)。store 表示了session 的二级存储设备。PersistentManagerBase 使用私有变量store 保存对二级存储设备的引用:

private Store store = null;

在持久化manager 中,session 对象可以备份也可换出。当备份会话对象时,会话对象将复制到store中,并且原始数据将保留在内存中。 因此,如果服务器崩溃,可以从store中检索活动会话对象。当内存中session 对象超过一定数量或某个session 长时间未被访问时,该 session 会被换出,它会被写到store 中。换出session 是为了节省内存。

在 tomcat4 中,PersistentManagerBase 实现了 java.lang.Runnable 接口,使用专一线程周期性的备份和换出session。下面是 run()方法实现:

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

像StandardManager 类中一样,processExpired()方法检查 session 对象是否过期。processPersistenceChecks方法会调用其他三个方法:

public void processPersistenceChecks() {
    processMaxIdleSwaps();
    processMaxActiveSwaps();
    processMaxIdleBackups();
}

在 tomcat5 中,PersistentManagerBase 类不再实现 java.lang.Runnable 接口。备份和换出 session 是在backgroundProcess()方法中完成的,该方法被与该 manager 关联的StandardContext 实例周期性调用。

换出和备份在如下子章节中讨论。

9.2.4.1 换出(Swap Out)

PersistentManagerBase 类在换出session 对象时要遵守一定规则。只有当活动的session 数量超过maxActiveSessions值限制,或某个session 已经过期时,才将其换出。

当有过多session 对象时,PersistentManagerBase 类实例只是很简单地将任意 session 对象换出,直到活动session 对象数量等于maxActiveSessions(见processMaxActiveSwaps()方法)。

当某个 session 对象长时间没被访问时,PersistentManagerBase 类使用2个变量来判断 session 对象是否要被换出:minIdleSwap 和 maxIdleSwap。若是 session 对象的 lastAccessedTime 超过 minIdleSwap 和maxIdleSwap 时,则要换出该session。若希望所有的session 都不被换出,可将 maxIdleSwap 的值置为负数(见processMaxIdleSwaps()方法)。

由于活动 session 对象可以被换出,所以,某个活动的 session可能在内存中,也可能在 store 中。因此,调用 findSession(String id)时, 会先在内存中查找 ,若内存中没有, 则从 store 中查找 。下面是PersistentManagerBase 类中findSession()方法实现:

public Session findSession(String id) throws IOException {
    Session session = super.findSession(id);
    if (session != null)
        return (session);

    // not found in memory, see if the Session is in the Store
    session = swapIn(id);
    // swapIn returns an active session in the Store
    return (session);
}

9.2.4.2 备份(Back-up)

不是所有的 session对象都会被备份,PersistentManagerBase 类只会备份那些空闲时间超过 maxIdleBackup值的session。该任务由processMaxIdleBackups()方法完成。

9.2.5 PersistentManager类

PersistentManager 类继承自 PersistentManagerBase 类,并没有添加其它方法,只是多了2个属性,如Listing 9.5:

Listing 9.5: The PersistentManager class

package org.apache.catalina.session;

public final class PersistentManager extends PersistentManagerBase {
    // The descriptive information about this implementation.
    private static final String info = "PersistentManager/1.0";

    // The descriptive name of this Manager implementation (for logging).
    protected static String name = "PersistentManager";

    public String getInfo() {
        return (this.info);
    }

    public String getName() {
        return (name);
    }
}

9.2.6 DistributedManager类

tomcat4 中提供了 DistributedManager 类,该类继承自 PersistentManagerBase 类。该类用于2个或多于2个节点的集群环境。一个节点指一个tomcat部署。集群中的节点可以在同一台物理机器,也可以在不同的物理机器。在集群环境中,每个节点必须使用 DistributedManager 实例作为其 manager,才能支持session 复制,这也是 DistributedManager 类的主要功能。

为了复制session,当创建或销毁 session 对象时,DistributedManager 实例会向其他节点发送消息。此外,集群中的节点也必须能够接收其它节点发送的消息。这样,集群中的任何节点都可以提供HTTP请求。

为 了 发 送 和 接 收 消 息 , 在 org.apache.catalina.cluster 包 内 有 一 些 可 供 使 用 的 工 具 类 。 其 中 ,ClusterSender 类用于发送消息,ClusterReceiver用于接收消息。

DistrbutedManager 实例的 createSession()方法要创建一个 session 对象存储在当前 DistrbutedManager 实例中,并使用ClusterSender 实例向其它节点发送消息。createSession()
方法实现如Listing 9.6:

Listing 9.6: The createSession method

public Session createSession() {
    Session session = super.createSession();
    ObjectOutputStream oos = null;

    ByteArrayOutputStream bos = null;
    ByteArraylnputStream 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);

        if(debug > 0)
            log("Replicating Session: "+session.getId());
    }
    catch (IOException e) {
        log("An error occurred when replicating Session: " +
        session.getId());
    }

    retun (session);
}

其中,createSession()方法先调用父类的 createSession()方法创建一个 session 对象,然后以字节数组的形式通过ClusterSender将session 对象发送到其他节点。

DistribubedManager 类还实现了 java.lang.Runnable 接口,使用专一线程来将某个 session 标识为过期,或接收其它节点发来的消息。run()方法实现如下:

public void run() {

    // Loop until the termination semaphore is set
    while (!threadDone) {
        threadSleep();

        processClusterReceiver();

        processExpires();
    processPersistenceChecks();
    }
}

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

9.3 Stores

由org.apache.catalina.Store接口表示的存储是一个组件,它为管理器管理的会话提供永久存储。 Store接口如Listing9.7所示。

Listing 9.7: The Store interface

package org.apache.catalina;

import java.beans.PropertyChangeListener;
import java.io.IOException;

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

其中save() 和 load()是2个比较重要的方法。save()方法将指定的 session 对象持久化到store 中。load(String id)方法则根据id 从 store中找到session,载入到内存中。keys()方法返回所有 session 的 id 数组。相关类的UML 示意图如图9.3所示:

这里写图片描述
图9.3: The Store interface and its implementations

如下子章节将讨论StoreBase, FileStore和 JDBCStore类。

9.3.1 StoreBase类

StoreBase 类是一个抽象类,提供了 store 基本功能。该类有2个直接子类:FileStore和 JDBCStore类。StoreBase 类并没有实现 Store接口的 save() 和 load() 方法,因为,这2个方法依赖于具体的持久化设备。

tomcat4 中,StoreBase 类实现了 java.lang.Runnable 接口,使用专一线程周期性的检查 session,从活动session 的集合中删除过期的session。下面在Tomcat4中StoreBase的 run()方法实现:

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

processExpires()方法获取所有的活动 session 对象,检查每个 session 的 lastAccessedTime 值,删除那些长时间不活动的session 对象。Listing 9.7是processExpires() 方法实现:

Listing 9.7: the processExpires method

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

在 tomcat5 中,不再使用专用线程调用 processExpires()方法,相反,由与该 store 相关联的PersistentManagerBase 实例的backgroundProcess()方法周期性地调用 processExpires()方法。

9.3.2 FileStore类

FileStore 类将session 对象持久化到某个文件中。文件名的格式为sessionid+‘.session’。文件位于临时的工作目录下,可以调用FileStore 类的 setDirectory()方法修改临时目录的位置。

save()方法使用 java.io.ObjectOutputStream 类将 session 对象进行序列化。因此,session 实例中存储的所有对象都要实现 java.lang.Serializable 接口。load()方法使用 java.io.ObjectInputStream 接口实现 session 的反序列化。

9.3.3 JDBCStore类

JDBCStore 类将 session 对象通过 jdbc 存入数据库中。因此,为了使用 JDBCStore,我们需要分别调用setDriverName ()和 setConnectionURL() 方法来设置数据库驱动和连接 url。

9.4 应用Demo

本章附带的应用Demo与第8章中类似。它使用默认连接器(Connector),并且把拥有一包装器(Wrapper)的上下文(Context)作为其主容器。然而,不同的是这个应用程序中的上下文有一个StandardManager实例来管理会话对象。要测试此应用程序,请使用第3个servlet示例:SessionServlet。此servlet由名为wrapper1的包装器表示。

注意:我们可以在myApp/WEB-INF/classes目录下找到SessionServlet。

应用程序有2个包,ex09.pyrmont.core和ex09.pyrmont.startup,并使用Catalina中各种类。在ex09.pyrmont.core包中有4个类:SimpleContextConfig,SimplePipeline,SimpleWrapper和SimpleWrapperValve。前3个类和第8章中相似。但是,在SimpleWrapperValve类中有二行新增代码。 ex09.pyrmont.startup包中有1个类:Bootstrap。

Bootstrap类在本节中第1个子章节中介绍,第2子章节介绍SimpleWrapperValve类。最后一小节讨论如何运行此应用Demo。

9.4.1 Bootstrap类

本 章 的 Bootstrap 类 与 第8 章 中 Bootstrap 类 区 别 在 于 , 本 章 的 Bootstrap 类 会 创 建 一 个org.apache.catalina.session.StandardManager 类的实例,并将之与 Context 相关联。

main ()方法会先设置“catalina.base”系统属性,并实例化 connector:

System.setProperty("catalina.base", System.getProperty("user.dir"));
Connector connector = new HttpConnector();

创建SessionServlet对应的wrapper1:

Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Session");
wrapper1.setServletclass("SessionServlet");

然后,创建StandardContext 对象,设置path和 docBase属性,并将wrapper1 添加到 context 中:

Context context = new StandardContext();
context.setPath("/myApp");
context.setDocBase("myApp");
context.addChild(wrapper1);

接下来,继续添加servlet映射。 该映射不同于第8章。我们使用/myApp/Session替换/Session。 这是因为我们将上下文的路径名设置为/ myApp。 路径名将用于发送会话cookie,如果路径也是/ myApp,那么浏览器将只会把cookie发送回此服务器。

context.addServletMapping("/myApp/Session", "Session");

请求此SessionServlet的URL如下:

http://localhost:8080/myApp/Session

如第8章一样,需要为context 创建监听器(listener)和 加载器(loader):

LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);

// here is our loader
Loader loader = new WebappLoader();

// associate the loader with the Context
context.setLoader(loader);
connector.setContainer (context);

接下来是本章的重点内容:创建manager,并使之与context关联:

Manager manager = new StandardManager();
context.setManager(manager);

最后,初始化并启动connector 和 context:

connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) context).start();

9.4.2 SimpleWrapperValve类

回想一下,在本章开头,我们提到servlet可以通过调用javax.servlet.http.HttpServletRequest接口的getSession()方法来获取session对象。 当getSession()方法被调用时,请求对象必须以某种方式调用与该上下文相关联的管理器。 管理器新创建一会话对象或返回已有的会话对象。对于请求对象能够访问管理器,它必须有权访问该上下文。 为了实现这个,在SimpleWrapperValve类的invoke()方法中,需要通过org.apache.catalina.Request接口的setContext()方法,传递上下文。 记住,SimpleWrapperValve类中invoke()方法会调用已查询到的servlet的service()方法。 因此,我们必须在调用service()方法之前设置好上下文。 SimpleWrapperValve的invoke()方法如Listing 9.8。 突出显示的行是对此类新添加的。

Listing 9.8: The invoke method of the SimpleWrapperValve class

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();
    request.setContext(context);
    //-------------------------------------
    // Allocate a servlet instance to process this request
    try {
      servlet = wrapper.allocate();
      if (hres!=null && hreq!=null) {
        servlet.service(hreq, hres);
      }
      else {
        servlet.service(sreq, sres);
      }
    }
    catch (ServletException e) {
    }
  }

我们可以访问包装器,因此我们可以通过调用Container接口的getParent()方法获取上下文。 请注意,包装器已添加到上下文。 一旦我们拥有上下文,我们就可以调用Request接口的setContext()方法。

正如本章开头所述,org.apache.catalina.connector.HttpRequestBase类中私有方法doGetSession()可以通过Context接口中getManager()方法来获取管理器。

// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)

manager = context.getManager();

一旦获取到管理器,那么获取或新建一session对象就是自然而然的了。

9.4.3运行Demo

在 windows 下,可以在工作目录下面如下运行该程序:

java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./ex09.pyrmont.startup.Bootstrap

在 Linux 下,使用冒号分开两个库:

java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./ex09.pyrmont.startup.Bootstrap

调用SessionServlet,可以使用下面的 URL 来请求:

http://localhost:8080/myApp/Session

SessionServlet使用会话对象来存储值。 此servlet显示前一个值和当前值在Session对象中,它还显示一个窗体让用户可以使用输入一个新值,如图9.4:

这里写图片描述
图9.4

9.5 小结

本章讨论了管理会话的组件——会话管理器。 解释了管理器的类型和管理器如何将会话对象持久化到存储中。 在本章结尾,我们学习了如何使用StandardManager构建运行servlet应用程序来使用会话存储数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值