Apache Tomcat 会话机制 Cookie 与 Session

HTTP 协议是一种状态的协议,WEB 服务器本身不能识别出哪些请求是同一个浏览器发出的,浏览器的每一个请求都是完全独立的。

即使 HTTP 1.1 支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。

怎么才能实现网上商店中的购物车呢:某个用户从网站的登陆页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求程序所得到的用户信息。

作为 Web 服务器,必须能够采用一种机制来唯一地标识一个标识,同时记录该用户的状态。

1、会话和会话状态

Web 应用中的会话是指一个客户端浏览器与 Web 服务器之间连接发生的一系统请求和响应过程。

Web 应用的会话状态是指 Web 服务器与浏览器在会话过程中产生的状态信息,借助会话状态, Web 服务器能够把属于同一会话中的一系列请求和响应过程关联起来

Web 服务器端程序要能从大量的请求消息中区分出哪些请求消息属于同一个会话,即能识别出来逢同一个浏览器的访问请求,这需要浏览器对其发出的每个请求消息都进行标识:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话 ID(SessionId)

在 Servlet 规范中,常用以下两种机制完成会话跟踪

  • Cookie
  • Session

2、HTTP Cookie

cookie 机制采用的是在**客户端保存 HTTP 状态信息的方案

Cookie 是在浏览器访问 Web 服务器的某个资源时,由 Web 服务器在 Http 响应头中附带传送给浏览器的一个小文本文件

一旦 Web 浏览器保存了某个 cookie,那么它在以后每次访问该 Web 服务器时,都会在 HTTP 请求头中将这个 Cookie 回传给 Web 服务器

底层的实现原理:Web 服务器通过在 HTTP 响应消息中增加 Set-Cookie 响应头 字段将 Cookie 信息发送给浏览器,浏览器则通过在 HTTP 请求信息中增加 Cookie 请求头字段将 Cookie 回传给 Web 服务器。

  • 一个 Cookie 只能标识一种信息,它至少含有一个标识该信息的名称(Name)和设置值(value)
  • 一个 Web 站点可以给一个 Web 浏览器发送多个 Cookie,一个 Web 浏览器也可以存储多个 Web 站点提供的 Cookie
  • 浏览器一般只允许存放 300 个 Cookie,每个站点最多存放 20 个 Cookie,每个 Cookie 的大小限制为 4 KB

Apache Tomcat 会话机制 Cookie 与 Session_第1张图片

HttpServletRequest 接口提供了getCookies方法来获取请求中出现的 cookies 数组。这些 cookie 是客户端在每次请求时从客户端发送到服务器的数据。通常,客户机作为 cookie 的一部分返回的唯一信息是 cookie 名称和 cookie 值。在将 cookie 发送到浏览器时可以设置的其他 cookie 属性,比如注释,通常不会返回。该规范还允许 cookie 为HttpOnly cookie。HttpOnly cookie向客户端表明它们不应该被暴露给客户端脚本代码(除非客户端知道寻找这个属性,否则它不会被过滤掉)。使用 HttpOnly cookie有助于减轻某些类型的跨站点脚本攻击。HttpServletResponse 接口提供了addCookie 方法来添加 cookie 对象。

Cookie 总结如下

  • 完成会话跟踪的一种机制:采用的是在客户端保持 HTTP 状态信息的方案
  • Cookie 是在浏览器访问 WEB 服务器的某个资源时,由 WEB 服务器在 HTTP 响应消息头附带传送给浏览器的一个小文本文件
  • 一旦 WEB 浏览器保存了某个 Cookie,那么它在以后每次访问该 WEB 服务器时,都会在 HTTP 请求头中将这个 Cookie 回传给 WEB 服务器

Cookie 操作工具类

public class CookieUtils {

    public static String getCookie(HttpServletRequest request, String cookieName){
        //Cookie的默认有效期是一次会话中。
        Cookie[] cookies = request.getCookies();
        String value = null;
        if(cookies != null){
            for (Cookie cookie : cookies) {
                //遍历出每一个cookie对象,咱们怎么去判断该cookie是否是我们想要的那个呢?
                //通过cookie的name进行判断
                String name = cookie.getName();
                if (name.equals(cookieName)) {
                    //获取cookie的value
                    value = cookie.getValue();
                    break;
                }
            }
        }
        return value;
    }

    public static void addCookie(HttpServletResponse response, String name, String value, int time, String path){
        // 1.创建Cookie对象
        Cookie cookie = new Cookie(name, value);
        // 2.设置该cookie的最大有效期
        cookie.setMaxAge(time);
        // 3.设置有效范围
        cookie.setPath(path);
        // 4.添加 cookie
        response.addCookie(cookie);
    }

    public static void deleteCookie(HttpServletResponse response, String name){
        // 1.创建Cookie对象
        Cookie cookie = new Cookie(name, null);
        // 2.设置该cookie的最大有效期
        cookie.setMaxAge(0);
        // 3.添加到 response
        response.addCookie(cookie);
    }

}

3、HTTP Session

session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称这为一个 Session。session 在 Web 开发环境下的语义以有了新的扩展,它的含义是指 一类用来在客户端与服务器端之间保持状态的解决方案。有时候 Session 也用来指这种解决方案的储存结构

session 机制采用的是在服务器端保持 HTTP 状态信息的方案。服务器使用散列表的结构来保存信息。当程序需要为某个客户端的请求创建一个 Session 时,服务器首先检查这个客户端的请求里是否包含了一个 Session 标识(即 sessionId),如果已经包含一个 sessionId 则说明以前已经为此客户创建过 session,服务器就按照 sessionId 把这个 session 检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的 session 对象,但用户人为地在请求的 URL 后面附加一个 JSESSION 的参数)。如果客户请求不包含 sessionId,则为此客户创建一个 session 并且生成一个与此 session 相关联的 sessionId,这个 sessionId 将在本次响应中返回给客户端保存。

HelloWorldServlet.java

public class HelloWorldServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		HttpSession session = req.getSession();
		session.setAttribute("name", "carl");
		System.out.println("invoke HelloWorldServlet.doGet success");
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("invoke HelloWorldServlet.doPost success");
	}
}

以chrome浏览器为例,访问一个基于tomcat服务器的网站的时候,

浏览器第一次访问服务器,服务器会在响应头添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客户端设置cookie,如下图:

接下来,浏览器第二次、第三次…访问服务器,观察其请求头的cookie信息,可以看到JSESSIONID信息存储在cookie里,发送给服务器;且响应头里没有Set-Cookie信息,如下图:

Apache Tomcat 会话机制 Cookie 与 Session_第2张图片
只要浏览器未关闭,在访问同一个站点的时候,其请求头Cookie中的JSESSIONID都是同一个值,被服务器认为是同一个会话。

4、Tomcat Session 实现

4.1 获取 Session 信息

当请求过程中首先要解析请求中的 sessionId 信息,然后将 sessionId 存储到 request 的参数列表中。然后再从 Request 获取 Session 的时候,如果存在 sessionId 那么就根据 Id 从 Session 池中获取 session,如果 sessionId 不存在或者 Session 失效,那么则新建 Session并且将 Session 信息放入缓存池,供下次使用。

sessionid 的默认 Key 是 jsessionid 一般客户端请求服务端时间保存在 2 个地方:

  • 正常情况下是保存请求 Http 请求的 Cookie 当中,请次请求的时候客户端都会带上 Cookie 信息,这个时候服务端就会通过 Cookie 获取到保存到服务端的 Session 信息。
  • 另外一种情况是 jsessionId 保存在 URL 信息当中。比如:http://localhost:8080/web-demo/servlets/hello;jsessionid=123456789。这种方式其实是当客户端禁用 Cookie,只能通过 URL 重写的方式把 sessionId 信息在禁用 Cookie 的情况下通过重写 URL 带到服务端。
    Apache Tomcat 会话机制 Cookie 与 Session_第3张图片
    上面的 CoyoteAdapter#parsePathParameters 就是 Cookie 禁用的情况下把 jsessionid 以 url 的形式传入到服务端:

CoyoteAdapter#parsePathParameters

    protected void parsePathParameters(org.apache.coyote.Request req,
            Request request) {

        // Process in bytes (this is default format so this is normally a NO-OP
        req.decodedURI().toBytes();

        ByteChunk uriBC = req.decodedURI().getByteChunk();
        
        // Cookie 禁用通过 URL 的方式传入到服务端,例如:http://localhost:8080/web-demo/servlets/hello;jsessionid=123456789
        int semicolon = uriBC.indexOf(';', 0);
        // Performance optimisation. Return as soon as it is known there are no
        // path parameters;
        if (semicolon == -1) {
            return;
        }

        // What encoding to use? Some platforms, eg z/os, use a default
        // encoding that doesn't give the expected result so be explicit
        Charset charset = connector.getURICharset();

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("coyoteAdapter.debug", "uriBC",
                    uriBC.toString()));
            log.debug(sm.getString("coyoteAdapter.debug", "semicolon",
                    String.valueOf(semicolon)));
            log.debug(sm.getString("coyoteAdapter.debug", "enc", charset.name()));
        }

        while (semicolon > -1) {
            // Parse path param, and extract it from the decoded request URI
            int start = uriBC.getStart();
            int end = uriBC.getEnd();

            int pathParamStart = semicolon + 1;
            int pathParamEnd = ByteChunk.findBytes(uriBC.getBuffer(),
                    start + pathParamStart, end,
                    new byte[] {';', '/'});

            String pv = null;

            if (pathParamEnd >= 0) {
                if (charset != null) {
                    pv = new String(uriBC.getBuffer(), start + pathParamStart,
                                pathParamEnd - pathParamStart, charset);
                }
                // Extract path param from decoded request URI
                byte[] buf = uriBC.getBuffer();
                for (int i = 0; i < end - start - pathParamEnd; i++) {
                    buf[start + semicolon + i]
                        = buf[start + i + pathParamEnd];
                }
                uriBC.setBytes(buf, start,
                        end - start - pathParamEnd + semicolon);
            } else {
                if (charset != null) {
                    pv = new String(uriBC.getBuffer(), start + pathParamStart,
                                (end - start) - pathParamStart, charset);
                }
                uriBC.setEnd(start + semicolon);
            }

            if (log.isDebugEnabled()) {
                log.debug(sm.getString("coyoteAdapter.debug", "pathParamStart",
                        String.valueOf(pathParamStart)));
                log.debug(sm.getString("coyoteAdapter.debug", "pathParamEnd",
                        String.valueOf(pathParamEnd)));
                log.debug(sm.getString("coyoteAdapter.debug", "pv", pv));
            }

            if (pv != null) {
                int equals = pv.indexOf('=');
                if (equals > -1) {
                    String name = pv.substring(0, equals);
                    String value = pv.substring(equals + 1);
                    // 解析出来的信息添加到请求参数当中
                    request.addPathParameter(name, value);
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("coyoteAdapter.debug", "equals",
                                String.valueOf(equals)));
                        log.debug(sm.getString("coyoteAdapter.debug", "name",
                                name));
                        log.debug(sm.getString("coyoteAdapter.debug", "value",
                                value));
                    }
                }
            }

            semicolon = uriBC.indexOf(';', semicolon);
        }
    }

上面是把 Url 重写的信息保存到 HTTP 请求信息当中。然后会在 CoyoteAdapter#postParseRequest 信息当中从 HTTP 请求参数当中获取 SessionId 信息。

上面会从两个地方获取 Session,首先从 Http 请求参数中获取也就是我们上一步的解析重写的 HTTP 看里面是否带 SessionId ,然后从 Cookie 里面获取 SessionId 信息。

    protected void parseSessionCookiesId(Request request) {

        // If session tracking via cookies has been disabled for the current
        // context, don't go looking for a session ID in a cookie as a cookie
        // from a parent context with a session ID may be present which would
        // overwrite the valid session ID encoded in the URL
        Context context = request.getMappingData().context;
        if (context != null && !context.getServletContext()
                .getEffectiveSessionTrackingModes().contains(
                        SessionTrackingMode.COOKIE)) {
            return;
        }

        // Parse session id from cookies
        ServerCookies serverCookies = request.getServerCookies();
        int count = serverCookies.getCookieCount();
        if (count <= 0) {
            return;
        }

        String sessionCookieName = SessionConfig.getSessionCookieName(context);

        for (int i = 0; i < count; i++) {
            ServerCookie scookie = serverCookies.getCookie(i);
            if (scookie.getName().equals(sessionCookieName)) {
                // Override anything requested in the URL
                if (!request.isRequestedSessionIdFromCookie()) {
                    // Accept only the first session id cookie
                    convertMB(scookie.getValue());
                    request.setRequestedSessionId
                        (scookie.getValue().toString());
                    request.setRequestedSessionCookie(true);
                    request.setRequestedSessionURL(false);
                    if (log.isDebugEnabled()) {
                        log.debug(" Requested cookie session id is " +
                            request.getRequestedSessionId());
                    }
                } else {
                    if (!request.isRequestedSessionIdValid()) {
                        // Replace the session id until one is valid
                        convertMB(scookie.getValue());
                        request.setRequestedSessionId
                            (scookie.getValue().toString());
                    }
                }
            }
        }

    }

从上面的代码可以看到如果 HTTP 的 URL 里面与 Cookie 信息里面同时存在 Session 信息那么就会以 Cookie 里面的 SessionId 为先。

4.2 生成 Session 对象

下面就是服务器端获取或者生成 Session 的时序图。
Apache Tomcat 会话机制 Cookie 与 Session_第4张图片
默认情况下,服务器端是没有 Session 信息的,只有在业务代码当中有 HttpServletRequest#getSession 才会触发服务端生成对应请求的 Session 信息。getSession 有两个重载方法:

  • getSession(boolean create):如果请求参数为 true 时,返回当前的 HttpSession 对象,如果没有则创建一个。如果请求参数为 false 时,返回当前的 HttpSession 对象,否则返回 null。、
  • getSession():这个方法相当于 getSession(true),如果请求参数为 true 时,返回当前的 HttpSession 对象,如果没有则创建一个。

在 Apache Tomcat 当中通过 org.apache.catalina.Manager 来管理 HttpSession 信息。

public interface Manager {
    // ------------------------------------------------------------- Properties
    public Context getContext();

    public void setContext(Context context);

    public SessionIdGenerator getSessionIdGenerator();

    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator);

    public long getSessionCounter();

    public void setSessionCounter(long sessionCounter);

    public int getMaxActive();

    public void setMaxActive(int maxActive);

    public int getActiveSessions();

    public long getExpiredSessions();

    public void setExpiredSessions(long expiredSessions);

    public int getRejectedSessions();

    public int getSessionMaxAliveTime();

    public void setSessionMaxAliveTime(int sessionMaxAliveTime);

    public int getSessionAverageAliveTime();

    public int getSessionCreateRate();

    public int getSessionExpireRate();
    // --------------------------------------------------------- Public Methods

    public void add(Session session);

    public void addPropertyChangeListener(PropertyChangeListener listener);

    public void changeSessionId(Session session);

    public void changeSessionId(Session session, String newId);

    public Session createEmptySession();

    public Session createSession(String sessionId);

    public Session findSession(String id) throws IOException;

    public Session[] findSessions();

    public void load() throws ClassNotFoundException, IOException;

    public void remove(Session session);

    public void remove(Session session, boolean update);

    public void removePropertyChangeListener(PropertyChangeListener listener);

    public void unload() throws IOException;

    public void backgroundProcess();

    public boolean willAttributeDistribute(String name, Object value);
}

里面包含了 Session 的管理方法:

  • Session Id 生成规则
  • 创建 Session
  • 删除 Session
  • 添加 Session 监听事件
  • 设置 Session 的有效时间

下面是 SessionId 的 key 规则,Cookie 的 SessionId 默认是 jsessionid,而重写 URL 里面的 SessionId 默认是 JSESSIONID

public class SessionConfig {

    private static final String DEFAULT_SESSION_COOKIE_NAME = "JSESSIONID";
    private static final String DEFAULT_SESSION_PARAMETER_NAME = "jsessionid";

    /**
     * Determine the name to use for the session cookie for the provided
     * context.
     * @param context The context
     * @return the cookie name for the context
     */
    public static String getSessionCookieName(Context context) {

        String result = getConfiguredSessionCookieName(context);

        if (result == null) {
            result = DEFAULT_SESSION_COOKIE_NAME;
        }

        return result;
    }

    /**
     * Determine the name to use for the session path parameter for the provided
     * context.
     * @param context The context
     * @return the parameter name for the session
     */
    public static String getSessionUriParamName(Context context) {

        String result = getConfiguredSessionCookieName(context);

        if (result == null) {
            result = DEFAULT_SESSION_PARAMETER_NAME;
        }

        return result;
    }


    private static String getConfiguredSessionCookieName(Context context) {

        // Priority is:
        // 1. Cookie name defined in context
        // 2. Cookie name configured for app
        // 3. Default defined by spec
        if (context != null) {
            String cookieName = context.getSessionCookieName();
            if (cookieName != null && cookieName.length() > 0) {
                return cookieName;
            }

            SessionCookieConfig scc =
                context.getServletContext().getSessionCookieConfig();
            cookieName = scc.getName();
            if (cookieName != null && cookieName.length() > 0) {
                return cookieName;
            }
        }

        return null;
    }


    /**
     * Determine the value to use for the session cookie path for the provided
     * context.
     *
     * @param context The context
     * @return the parameter name for the session
     */
    public static String getSessionCookiePath(Context context) {

        SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig();

        String contextPath = context.getSessionCookiePath();
        if (contextPath == null || contextPath.length() == 0) {
            contextPath = scc.getPath();
        }
        if (contextPath == null || contextPath.length() == 0) {
            contextPath = context.getEncodedPath();
        }
        if (context.getSessionCookiePathUsesTrailingSlash()) {
            // Handle special case of ROOT context where cookies require a path of
            // '/' but the servlet spec uses an empty string
            // Also ensure the cookies for a context with a path of /foo don't get
            // sent for requests with a path of /foobar
            if (!contextPath.endsWith("/")) {
                contextPath = contextPath + "/";
            }
        } else {
            // Only handle special case of ROOT context where cookies require a
            // path of '/' but the servlet spec uses an empty string
            if (contextPath.length() == 0) {
                contextPath = "/";
            }
        }

        return contextPath;
    }


    private SessionConfig() {
        // Utility class. Hide default constructor.
    }
}

默认情况下 Session 会保存在 org.apache.catalina.session.ManagerBase#sessions 这个 ConcurrentHashMap 当中。

    @Override
    public Session createSession(String sessionId) {

        if ((maxActiveSessions >= 0) &&
                (getActiveSessions() >= maxActiveSessions)) {
            rejectedSessions++;
            throw new TooManyActiveSessionsException(
                    sm.getString("managerBase.createSession.ise"),
                    maxActiveSessions);
        }

        // Recycle or create a Session instance
        Session session = createEmptySession();

        // Initialize the properties of the new session and return it
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
        String id = sessionId;
        if (id == null) {
            id = generateSessionId();
        }
        session.setId(id);
        sessionCounter++;

        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
        synchronized (sessionCreationTiming) {
            sessionCreationTiming.add(timing);
            sessionCreationTiming.poll();
        }
        return session;
    }
  • 首先会创建一个空的 Session
  • 接着会设置 Session 的值
  • 然后生成 SessionId 并设置 SessionId

在设置 SessionId 的时候会调用 org.apache.catalina.session.ManagerBase#add 把 Session 添加到org.apache.catalina.session.ManagerBase#sessions 当中。同样的如果从 URL 或者 Cookie 当中解析到 SessionId 就会把这个 Map 对象当中获取 Session 值。

4.3 存储 Session 信息

通过上面我们可以知道 Tomcat 是通过 Manage 这个组件来管理 Session 的。下面我们来看一下 Manage 的类继承结构:

Apache Tomcat 会话机制 Cookie 与 Session_第5张图片

  • Manager:定义了关联到某一个容器的用来管理session池的基本接口。
  • ManagerBase:实现了Manager接口,该类提供了Session管理器的常见功能的实现。
  • StandardManager:继承自ManagerBase,tomcat的默认Session管理器(不指定配置,默认使用这 个),是tomcat处理session的非集群实现(也就说是单机版的),tomcat关闭时,内存session信息会持久化到磁盘保存为 SESSION.ser,再次启动时恢复。
  • PersistentManagerBase:继承自ManagerBase,实现了和定义了session管理器持久化的基础功能。
  • PersistentManager:继承自PersistentManagerBase,主要实现的功能是会把空闲的会话对象(通过设定超时时间)交换到磁盘上。
  • ClusterManager:实现了Manager接口,通过类名应该能猜到,这个就是管理集群session的管理器和上面那个 StandardManager单机版的session管理器是相对的概念。这个类定义类集群间session的复制共享接口。
  • ClusterManagerBase:实现了ClusterManager接口,继承自ManagerBase。该类实现了session复制的基本操作。
  • BackupManager:继承自ClusterManagerBase, 集群间session复制策略的一种实现,会话数据只有一个备份节点,这个备份节点的位置集群中所有节点都可见。这种设计使它有个优势就是支持异构部署。
  • DeltaManager:继承自ClusterManagerBase,集群建session复制策略的一种实现,和BackupManager不同的是,会话数据会复制到集群中所有的成员节点,这也就要求集群中所有节点必须同构,必须部署相同的应用。

在 PersistentManagerBase 类中有个成员变量 Store,通过它使用 Manage 具有持久化的能力:

PersistentManagerBase.java

    /**
     * Store object which will manage the Session store.
     */
    protected Store store = null;

持久化 session 管理器的存储策略就是有这个 Store 对象定义的,这个 Store 的类继承结构如下:

Apache Tomcat 会话机制 Cookie 与 Session_第6张图片

接口 Store 及其实例是为 session 管理器提供了一套存储策略,store定义了基本的接口,而 StoreBase提供了基本的实现。 其中FileStore类实现的策略是将session存储在以setDirectory()指定目录并以.session结尾的文件中的。 JDBCStore类是将Session通过JDBC存入数据库中,因此需要使用JDBCStore,需要分别调用setDriverName()方法和 setConnectionURL()方法来设置驱动程序名称和连接URL。

5、使用 Mysql 持久 Session

默认的Tomcat默认会话存储机制使用临时文件。为了节省使用JDBC和MySQL的会话,遵循以下过程:

  • 创建一个表来保存会话记录。
  • 确保Tomcat能够访问适当的JDBC驱动程序。
  • 修改适当的Tomcat配置文件,以指定对相关应用程序上下文使用持久 Session 管理器。

这些步骤都不涉及以任何方式修改示例会话脚本。反映Tomcat如何实现应用程序级别以上的会话支持。

5.1 创建一个表存储 Tomcat session.

Tomcat在会话表中存储了几种类型的信息:

  • 会话ID。默认情况下,ID是32个字符的MD5值。
  • 应用程序的名字。
  • 会话数据。这是一个序列化的字符串。
  • 会话是否有效,作为单个字节。
  • 允许的最大不活动时间,以秒为单位的32位整数。
  • 最后访问时间,作为64位整数。

下表满足这些规格;现在创建它,然后继续:

CREATE TABLE tomcat_session
(
    id VARCHAR(32) NOT NULL,
    app VARCHAR(255),
    data LONGBLOB,
    valid_session CHAR(1) NOT NULL,
    max_inactive INT NOT NULL,
    update_time BIGINT NOT NULL,
    PRIMARY KEY (id),
    INDEX (app)
);

5.2 添加 JDBC 驱动

将JDBC驱动程序放在Tomcat可以找到它的地方。

因为Tomcat本身管理会话,所以它必须能够访问JDBC驱动程序用于在数据库中存储会话。通常在lib目录中安装驱动程序,以便对Tomcat和应用程序都可用。(备注:如果 war 中中已经有引用mysql jdbc驱动程序则不需要专门将驱动jar包拷贝到tomcat的自由目录下)

5.3 修改 Tomcat 配置文件

要告诉Tomcat使用tomcat_session表,请修改mcb应用程序上下文文件。更改位置到webapps/mcb/META-INF下的 Tomcat/webapps 目录,复制context.xml。jdbc上下文。xml,并重新启动 Tomcat。

如果你看一下context.xml中,你会发现一个元素包含一个元素,该元素指定使用JDBC存储基于mysql的会话:

context.xml




    

        
    


5.4 HelloWorldServlet

编写一个获取 Session 的 HelloWorldServlet 并且存在数据到 Session 当中。

public class HelloWorldServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		HttpSession session = req.getSession();
		session.setAttribute("name", "carl");
		System.out.println("invoke HelloWorldServlet.doGet success");
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("invoke HelloWorldServlet.doPost success");
	}
}

5. 5 运行结果

在 web.xml 中添加 以上的 Servlet 。

访问 http://localhost:8080/web-demo/servlets/hello,然后关闭 Tomcat。可以在数据库 Session 表中看到新增了一条数据:

Apache Tomcat 会话机制 Cookie 与 Session_第7张图片

参考文章:

  • Tomcat中session的管理机制
  • Tomcat生成的session持久化到MySQL

你可能感兴趣的:(Tomcat,Java)