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。

posted @ 2016-10-31 16:18  coldridgeValley  阅读(523)  评论(0编辑  收藏  举报