Tomcat Session管理机制(Tomcat源码解析七)

前面几篇我们分析了Tomcat的启动,关闭,请求处理的流程,tomcat的classloader机制,本篇将接着分析Tomcat的session管理方面的内容。

在开始之前,我们先来看一下总体上的结构,熟悉了总体结构以后,我们在一步步的去分析源代码。Tomcat session相光的类图如下:

通过上图,我们可以看出每一个StandardContext会关联一个Manager,默认情况下Manager的实现类是StandardManager,而StandardManager内部会聚合多个Session,其中StandardSession是Session的默认实现类,当我们调用Request.getSession的时候,Tomcat通过StandardSessionFacade这个外观类将StandardSession包装以后返回。

上面清楚了总体的结构以后,我们来进一步的通过源代码来分析一下。咋们首先从Request的getSession方法看起。

org.apache.catalina.connector.Request#getSession
public HttpSession getSession() {
    Session session = doGetSession(true);
    if (session == null) {
        return null;
    }

    return session.getSession();
}

从上面的代码,我们可以看出首先首先调用doGetSession方法获取Session,然后再调用Session的getSession方法返回HttpSession,那接下来我们再来看看doGetSession方法:

org.apache.catalina.connector.Request#doGetSession
protected Session 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);
    }

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

    // Create a new session if requested and the response is not committed
    // 3
    if (!create) {
        return (null);
    }
    if ((context != null) && (response != null) &&
        context.getServletContext().getEffectiveSessionTrackingModes().
                contains(SessionTrackingMode.COOKIE) &&
        response.getResponse().isCommitted()) {
        throw new IllegalStateException
          (sm.getString("coyoteRequest.sessionCreateCommitted"));
    }

    // Attempt to reuse session id if one was submitted in a cookie
    // Do not reuse the session id if it is from a URL, to prevent possible
    // phishing attacks
    // Use the SSL session ID if one is present.
    // 4
    if (("/".equals(context.getSessionCookiePath())
            && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
        session = manager.createSession(getRequestedSessionId());
    } else {
        session = manager.createSession(null);
    }

    // Creating a new session cookie based on that session
    if ((session != null) && (getContext() != null)
           && getContext().getServletContext().
                   getEffectiveSessionTrackingModes().contains(
                           SessionTrackingMode.COOKIE)) {
        // 5
        Cookie cookie =
            ApplicationSessionCookieConfig.createSessionCookie(
                    context, session.getIdInternal(), isSecure());

        response.addSessionCookieInternal(cookie);
    }

    if (session == null) {
        return null;
    }

    session.access();
    return session;
}

下面我们就来重点分析一下,上面代码中标注了数字的地方:

  1. 标注1(第17行)首先从StandardContext中获取对应的Manager对象,缺省情况下,这个地方获取的其实就是StandardManager的实例。
  2. 标注2(第26行)从Manager中根据requestedSessionId获取session,如果session已经失效了,则将session置为null以便下面创建新的session,如果session不为空则通过调用session的access方法标注session的访问时间,然后返回。
  3. 标注3(第43行)判断传递的参数,如果为false,则直接返回空,这其实就是对应的Request.getSession(true/false)的情况,当传递false的时候,如果不存在session,则直接返回空,不会新建。
  4. 标注4 (第59行)调用Manager来创建一个新的session,这里默认会调用到StandardManager的方法,而StandardManager继承了ManagerBase,那么默认其实是调用了了ManagerBase的方法。
  5. 标注5 (第72行)创建了一个Cookie,而Cookie的名称就是大家熟悉的JSESSIONID,另外JSESSIONID其实也是可以配置的,这个可以通过context节点的sessionCookieName来修改。比如….

通过doGetSession获取到Session了以后,我们发现调用了session.getSession方法,而Session的实现类是StandardSession,那么我们再来看下StandardSession的getSession方法。

org.apache.catalina.session.StandardSession#getSession
public HttpSession getSession() {

    if (facade == null){
        if (SecurityUtil.isPackageProtectionEnabled()){
            final StandardSession fsession = this;
            facade = AccessController.doPrivileged(
                    new PrivilegedAction<StandardSessionFacade>(){
                @Override
                public StandardSessionFacade run(){
                    return new StandardSessionFacade(fsession);
                }
            });
        } else {
            facade = new StandardSessionFacade(this);
        }
    }
    return (facade);

}

通过上面的代码,我们可以看到通过StandardSessionFacade的包装类将StandardSession包装以后返回。到这里我想大家应该熟悉了Session创建的整个流程。

接着我们再来看看,Sesssion是如何被销毁的。我们在Tomcat启动过程(Tomcat源代码阅读系列之三)中之处,在容器启动以后会启动一个ContainerBackgroundProcessor线程,这个线程是在Container启动的时候启动的,这条线程就通过后台周期性的调用org.apache.catalina.core.ContainerBase#backgroundProcess,而backgroundProcess方法最终又会调用org.apache.catalina.session.ManagerBase#backgroundProcess,接下来我们就来看看Manger的backgroundProcess方法。

org.apache.catalina.session.ManagerBase#backgroundProcess
public void backgroundProcess() {
    count = (count + 1) % processExpiresFrequency;
    if (count == 0)
        processExpires();
}

上面的代码里,需要注意一下,默认情况下backgroundProcess是每10秒运行一次(StandardEngine构造的时候,将backgroundProcessorDelay设置为了10),而这里我们通过processExpiresFrequency来控制频率,例如processExpiresFrequency的值默认为6,那么相当于没一分钟运行一次processExpires方法。接下来我们再来看看processExpires。

org.apache.catalina.session.ManagerBase#processExpires
public void processExpires() {

    long timeNow = System.currentTimeMillis();
    Session sessions[] = findSessions();
    int expireHere = 0 ;

    if(log.isDebugEnabled())
        log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
    for (int i = 0; i < sessions.length; i++) {
        if (sessions[i]!=null && !sessions[i].isValid()) {
            expireHere++;
        }
    }
    long timeEnd = System.currentTimeMillis();
    if(log.isDebugEnabled())
         log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
    processingTime += ( timeEnd - timeNow );

}

上面的代码比较简单,首先查找出当前context的所有的session,然后调用session的isValid方法,接下来我们在看看Session的isValid方法。

org.apache.catalina.session.StandardSession#isValid
public boolean isValid() {

    if (this.expiring) {
        return true;
    }

    if (!this.isValid) {
        return false;
    }

    if (ACTIVITY_CHECK && accessCount.get() > 0) {
        return true;
    }

    if (maxInactiveInterval > 0) {
        long timeNow = System.currentTimeMillis();
        int timeIdle;
        if (LAST_ACCESS_AT_START) {
            timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);
        } else {
            timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
        }
        if (timeIdle >= maxInactiveInterval) {
            expire(true);
        }
    }

    return (this.isValid);
}

查看上面的代码,主要就是通过对比当前时间和上次访问的时间差是否大于了最大的非活动时间间隔,如果大于就会调用expire(true)方法对session进行超期处理。这里需要注意一点,默认情况下LAST_ACCESS_AT_START为false,读者也可以通过设置系统属性的方式进行修改,而如果采用LAST_ACCESS_AT_START的时候,那么请求本身的处理时间将不算在内。比如一个请求处理开始的时候是10:00,请求处理花了1分钟,那么如果LAST_ACCESS_AT_START为true,则算是否超期的时候,是从10:00算起,而不是10:01。

接下来我们再来看看expire方法,代码如下:

org.apache.catalina.session.StandardSession#expire
public void expire(boolean notify) {

    // Check to see if expire is in progress or has previously been called
    if (expiring || !isValid)
        return;

    synchronized (this) {
        // Check again, now we are inside the sync so this code only runs once
        // Double check locking - expiring and isValid need to be volatile
        if (expiring || !isValid)
            return;

        if (manager == null)
            return;

        // Mark this session as "being expired"
        // 1
        expiring = true;

        // Notify interested application event listeners
        // FIXME - Assumes we call listeners in reverse order
        Context context = (Context) manager.getContainer();

        // The call to expire() may not have been triggered by the webapp.
        // Make sure the webapp's class loader is set when calling the
        // listeners
        ClassLoader oldTccl = null;
        if (context.getLoader() != null &&
                context.getLoader().getClassLoader() != null) {
            oldTccl = Thread.currentThread().getContextClassLoader();
            if (Globals.IS_SECURITY_ENABLED) {
                PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                        context.getLoader().getClassLoader());
                AccessController.doPrivileged(pa);
            } else {
                Thread.currentThread().setContextClassLoader(
                        context.getLoader().getClassLoader());
            }
        }
        try {
            // 2
            Object listeners[] = context.getApplicationLifecycleListeners();
            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 {
                        context.fireContainerEvent("beforeSessionDestroyed",
                                listener);
                        listener.sessionDestroyed(event);
                        context.fireContainerEvent("afterSessionDestroyed",
                                listener);
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        try {
                            context.fireContainerEvent(
                                    "afterSessionDestroyed", listener);
                        } catch (Exception e) {
                            // Ignore
                        }
                        manager.getContainer().getLogger().error
                            (sm.getString("standardSession.sessionEvent"), t);
                    }
                }
            }
        } finally {
            if (oldTccl != null) {
                if (Globals.IS_SECURITY_ENABLED) {
                    PrivilegedAction<Void> pa =
                        new PrivilegedSetTccl(oldTccl);
                    AccessController.doPrivileged(pa);
                } else {
                    Thread.currentThread().setContextClassLoader(oldTccl);
                }
            }
        }

        if (ACTIVITY_CHECK) {
            accessCount.set(0);
        }
        setValid(false);

        // Remove this session from our manager's active sessions
        // 3
        manager.remove(this, true);

        // Notify interested session event listeners
        if (notify) {
            fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
        }

        // Call the logout method
        if (principal instanceof GenericPrincipal) {
            GenericPrincipal gp = (GenericPrincipal) principal;
            try {
                gp.logout();
            } catch (Exception e) {
                manager.getContainer().getLogger().error(
                        sm.getString("standardSession.logoutfail"),
                        e);
            }
        }

        // We have completed expire of this session
        expiring = false;

        // Unbind any objects associated with this session
        // 4
        String keys[] = keys();
        for (int i = 0; i < keys.length; i++)
            removeAttributeInternal(keys[i], notify);

    }

}

上面代码的主流程我已经标注了数字,我们来逐一分析一下:

  1. 标注1(第18行)标记当前的session为超期
  2. 标注2(第41行)出发HttpSessionListener监听器的方法。
  3. 标注3(第89行)从Manager里面移除当前的session
  4. 标注4(第113行)将session中保存的属性移除。

到这里我们已经清楚了Tomcat中对与StandardSession的创建以及销毁的过程,其实StandardSession仅仅是实现了内存中Session的存储,而Tomcat还支持将Session持久化,以及Session集群节点间的同步。这些内容我们以后再来分析。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-29 02:56:15

Tomcat Session管理机制(Tomcat源码解析七)的相关文章

grunt源码解析:整体运行机制&amp;grunt-cli源码解析

前端的童鞋对grunt应该不陌生,前面也陆陆续续的写了几篇grunt入门的文章.本篇文章会更进一步,对grunt的源码进行分析.文章大体内容内容如下: grunt整体设计概览 grunt-cli源码分析 grunt-cli模块概览 grunt-cli源码分析 写在后面 grunt整体设计概览 grunt主要由三部分组成.其中,grunt-cli是本文的讲解重点 grunt-cli:命令行工具,调用本地安装的grunt来运行任务,全局安装. grunt:本地grunt,一般安装在项目根目录下.主要

JDK1.8 动态代理机制及源码解析

动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的方法,来实现代理. 缺点:被代理类必须实现一个或多个接口 参考链接:http://rejoy.iteye.com/blog/1627405 源码解析:见第四部分 cglib 动态代理 核心思想:通过生成子类字节码实现,代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个

volley源码解析(七)--最终目的之Response&lt;T&gt;

在上篇文章中,我们最终通过网络,获取到了HttpResponse对象 HttpResponse是android包里面的一个类,然后为了更高的扩展性,我们在BasicNetwork类里面看到,Volley将其包装成一个Volley自己的对象NetworkResponse 另外,在BasicNetwork类中我们也注意到,对HttpResponse包装成NetworkResponse的过程中,使用HttpResponse的Inputstream,将数据保存在一个byte[]数组中. BasicNet

Android异步消息处理机制(2)源码解析

上一章讲解了Android异步消息处理机制的基本使用,下面将简单地探寻一下异步机制背后的奥妙,源码版本为:API22. 首先,声明一下本文是在我参考了一下各位大神的文章之后才慢慢熟悉的, 若有不足之处,还望各位批评指正!.菜鸟上路,,,, 郭霖博客 鸿洋博客 刘超 深入解析android5.0系统 任玉刚博客 先后顺序按照拼音排序,无关技术本身. 先简单地总结一下Looper,MessageQueue,Message和Handler四者之间的关系: Looper和MessageQueue Loo

jdk1.8ArrayList主要方法和扩容机制(源码解析)

参见博客:https://blog.csdn.net/u010890358/article/details/80515284?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task   基于jdk1.8涵盖所有方法,有些地方解释牵强不好理解. 补充博客:https://blog.csdn.net/u010250240/article/details/897629

Celery 源码解析七:Worker 之间的交互

前面对于 Celery 的分布式处理已经做了一些介绍,例如第五章的 远程控制 和第六章的 Event机制,但是,我认为这些分布式都比较简单,并没有体现出多实例之间的协同作用,所以,今天就来点更加复杂的,对于多实例直接的交互更多,这就是 Gossip 和 Mingle. Mingle 在 Celery 的介绍中,Mingle 主要用在启动或者重启的时候,它会和其他的 worker 交互,从而进行同步.同步的数据有: 其他 worker 的 clock 其他 worker 已经处理掉的 tasks

jQuery 源码解析(七) jQuery对象和DOM对象的互相转换

jQuery对象是一个类数组对象,它保存的是对应的DOM的引用,我们可以直接用[]获取某个索引内的DOM节点,也可以用get方法获取某个索引内的DOM节点,还可以用toArray()方法把jQuery对象一次性转换成一个数组,例如: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title

spark内存管理器--MemoryManager源码解析

MemoryManager内存管理器 内存管理器可以说是spark内核中最重要的基础模块之一,shuffle时的排序,rdd缓存,展开内存,广播变量,Task运行结果的存储等等,凡是需要使用内存的地方都需要向内存管理器定额申请.我认为内存管理器的主要作用是为了尽可能减小内存溢出的同时提高内存利用率.旧版本的spark的内存管理是静态内存管理器StaticMemoryManager,而新版本(应该是从1.6之后吧,记不清了)则改成了统一内存管理器UnifiedMemoryManager,同一内存管

事件分发机制之 源码解析

事件的下发:dispatchTouchEvent ViewGroup相关事件有三个:onInterceptTouchEvent.dispatchTouchEvent.onTouchEvent View相关事件有两个:dispatchTouchEvent.onTouchEvent 简单来说就是:当一个Touch事件到达根节点时,它会依次[下发],下发的过程是调用子View的dispatchTouchEvent方法实现的. 详细来说就是:ViewGroup遍历它包含着的[子View],如果Touch