zoukankan      html  css  js  c++  java
  • Tomcat的Session管理(二)

    摘要:本文主要介绍了session的过期处理

    在上一篇http://www.cnblogs.com/coldridgeValley/p/6016211.html中,我们讲解了session相关的架构以及session的查找,创建。今天我们继续讲解一些session相关的内容。

      protected Session doGetSession(boolean create) {
    
        // There cannot be a session if no context has been assigned yet
        Context context = getContext();
        if (context == null) {
            return (null);
        }
    
        //11
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        if (session != null) {
            return (session);
        }
    	//省略
    }
    

    上一篇文章在讲解获取session的时候有一行代码一笔带过,就是

    session.isValid()
    

    这行代码其实内部内容很多,我们来查看下。

    /**
     * Return the <code>isValid</code> flag for this session.
     */
    @Override
    public boolean isValid() {
    	//如果是无效 返回无效
        if (!this.isValid) {
            return false;
        }
    	//如果已经过期了,就返回有效
        if (this.expiring) {
            return true;
        }
    	//该session的访问次数大于0 有效session
        if (ACTIVITY_CHECK && accessCount.get() > 0) {
            return true;
        }
    	//如果 允许的最大闲置时间大于0,也就是存在最大闲置时间
        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);
            }
    		//如果空闲时间 大于允许的最大闲置时间,那么就将session置为过期
            if (timeIdle >= maxInactiveInterval) {
                expire(true);
            }
        }
    
        return this.isValid;
    }
    

    expire(true)源码如下,部分删减:

      public void expire(boolean notify) {
    
    	//再次检查 isValid属性因为有可能已经被别的线程置为无效,也意味着该session已经被过期处理
        if (!isValid)
            return;
    	//锁定该session对象
        synchronized (this) {
    
    		//再次检查
            if (expiring || !isValid)
                return;
    		
            if (manager == null)
                return;
    
            //将expiring 设置为过期
            expiring = true;
    
            // Notify interested application event listeners 
            // FIXME - Assumes we call listeners in reverse order
            Context context = (Context) manager.getContainer();
    
            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 {
    			//获取Context的所有的监听器,然后触发 beforeSessionDestroyed 和  afterSessionDestroyed事件
                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);
            }
    		
    		//触发SESSION_DESTROYED_EVENT事件,区别于上面是所触发的监听器不同
            if (notify) {
                fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
            }
    
            // session注销,对应的用户登出
            if (principal instanceof GenericPrincipal) {
                GenericPrincipal gp = (GenericPrincipal) principal;
                try {
                    gp.logout();
                } catch (Exception e) {
                    manager.getContainer().getLogger().error(
                            sm.getString("standardSession.logoutfail"),
                            e);
                }
            }
    
            // 设置成无效的session
            setValid(false);
            expiring = false;
    
            String keys[] = keys();
            if (oldTccl != null) {
                if (Globals.IS_SECURITY_ENABLED) {
                    PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                            context.getLoader().getClassLoader());
                    AccessController.doPrivileged(pa);
                } else {
                    Thread.currentThread().setContextClassLoader(
                            context.getLoader().getClassLoader());
                }
            }
            try {
    			//remove掉所有session内部的对象
                for (int i = 0; i < keys.length; i++) {
                    removeAttributeInternal(keys[i], notify);
                }
            } finally {
                if (oldTccl != null) {
                    if (Globals.IS_SECURITY_ENABLED) {
                        PrivilegedAction<Void> pa =
                            new PrivilegedSetTccl(oldTccl);
                        AccessController.doPrivileged(pa);
                    } else {
                        Thread.currentThread().setContextClassLoader(oldTccl);
                    }
                }
            }
        }
    }
    

    思路还是很清楚,具体的代码就不深究了,感兴趣可以自行了解。可以看出一个session过期需要做很多事情,比如触发对应的生命周期事件,移除session内部对象等等。

    仔细查看expire()代码不难发现,代码内部一再检查isValid属性,理论上同一个用户只会同时开一个线程,也就是同时只有一个线程在更改session相关的属性,为何注释要写防止别的线程已更改呢?其实Catalina内部还有个定时线程在不停地跑过期session,这个也比较好理解,想想如果有很多session生成后从来不调用getSession()方法,岂不是永远不会被过期销毁?

    在上一篇关于ContainerBase中,我们介绍了了在StandardEngine中有个backgroundProcess()方法是专门做一些周期性任务,其中就有ManagerStandardManager)的一些方法调用。

     @Override
    public void backgroundProcess() {
        
        //。。省略部分代码
        if (manager != null) {
            try {
                manager.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);                
            }
        }
     	//。。。省略部分代码
    }
    
     /**
     * The Manager implementation with which this Container is associated.
     */
    protected Manager manager = null;
    

    这里的manager默认的就是StandardManager(具体实现类可以在启动时,createStartDigester()中寻找),再来查看StandardManagerbackgroundProcess()源码(最后在其父类ManagerBase中):

    @Override
    public void backgroundProcess() {
        count = (count + 1) % processExpiresFrequency;
        if (count == 0)
            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++) {
    		//调用isValid() 处理过期session
            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 );
    
    }
    

    代码非常简单,可以看出不管是主动getSession()还是后台周期任务处理,都会调用isValid()方法处理过期session。

  • 相关阅读:
    高精度模板 支持各种运算 c++
    404 页面不存在
    多柱汉诺塔问题“通解”——c++
    高精度gcd
    404 页面不存在
    如何开启音乐二倍速?不下载其他软件【win10】
    如何随时学习数学
    洛谷P1004 方格取数
    Title
    Title
  • 原文地址:https://www.cnblogs.com/coldridgeValley/p/6016274.html
Copyright © 2011-2022 走看看