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()
方法是专门做一些周期性任务,其中就有Manager
(StandardManager
)的一些方法调用。
@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()
中寻找),再来查看StandardManager
的backgroundProcess()
源码(最后在其父类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。