前段时间看了阵子Tomcat的源码,有所得。早想总结出来可惜太多了,不知道从何说起,今天早上有时间先从Session如何实现的说起吧,免得时间越久忘记的越多。
Tomcat的容器有四个级别,分别为Engine、Host、Context和Wrapper。所有的容器都有相同的父类,即org.apache.catalina.core.ContainerBase。那么session的管理在哪个级别的,Engine、Host还是Context?答案是在Context级别实现的,即每个应用有自己的session管理对象。
StandardContext是Context容器(即你的应用)的标准实现,在其start方法中(即启动时候)将创建该应用的session管理对象,非集群的情况下Session管理对象类为org.apache.catalina.session.StandardManager,它是session的标准管理器,每个Context(应用)关联一个,它实现了session的创建、查询、过期检测和序列化和反序列化,序列化和反序列化是在容器重启过程中用到的,保证重启不会丢失活动的session。
StandardContext的父类是org.apache.catalina.session.ManagerBase,其session的保存变量声明为protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();即利用ConcurrentHashMap保存session,如果想所有的应用共用一个session可以先将此变量设置为static的,然后修改设置cookie的JSESSION的path为“/”。
Session的创建——当request.getSession时候,根据请求中cookie中的或者url后的JSESSION参数查找session,如果存在则返回相应的session,不存在则创建并在响应(response)设置头信息,要求设置客户端cookie的JSESSION为session的id,path为应用的路径。
Session的过期检测——session的超时检测如何实现的呢?
在ContainerBase中有个ContainerBackgroundProcessor类。该类如下:
protected class ContainerBackgroundProcessor implements Runnable {
public void run() {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
;
}
if (!threadDone) {
Container parent = (Container) getMappingObject();
ClassLoader cl =
Thread.currentThread().getContextClassLoader();
if (parent.getLoader() != null) {
cl = parent.getLoader().getClassLoader();
}
processChildren(parent, cl);
}
}
}
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
container.backgroundProcess();
} catch (Throwable t) {
log.error("Exception invoking periodic operation: ", t);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i], cl);
}
}
}
}
当容器启动中会创建此类并运行,该类为多线程实现,即相当于启动一个与该容器相关的后台运行的线程。这个run方法中递归调用容器的processChildren方法,该方法首先运行容器相关的backgroundProcess方法,然后再递归调用子容器的processChildren,在backgroundProcess方法中会启动可能与容器相关的各种后台程序。其代码如下:
public void backgroundProcess() {
if (!started)
return;
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
}
}
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);
}
}
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
lifecycle.fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
其中如果为Context容器则会启动manager,此manager即为StandardManager,在其父类ManagerBase中的backgroundProcess方法如下:
/**
* Implements the Manager interface, direct call to processExpires
*/
public void backgroundProcess() {
count = (count + 1) % processExpiresFrequency;
if (count == 0)
processExpires();
}
processExpiresFrequency默认为6,相当于每过6秒检测一次,检测方法主要调用session的isValid方法,该方法判断当前时间与最近访问时间间隔是否超过设定的时间间隔。
另外,每次访问应用,都会调用StandardSession的access方法,即更新最近访问时间。
要工作了,最后还有session的监听的实现, 注册监听是在应用的web.xml中声明的,那么相应的监听也是保存在Context容器的变量中,其getApplicationLifecycleListeners()方法获得容器的监听数组,其声明为:
private transient Object applicationLifecycleListenersObjects[] =
new Object[0];
在StandardSession的expire方法如下:
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 {
fireContainerEvent(context,
"beforeSessionDestroyed",
listener);
listener.sessionDestroyed(event);
fireContainerEvent(context,
"afterSessionDestroyed",
listener);
} catch (Throwable t) {
try {
fireContainerEvent(context,
"afterSessionDestroyed",
listener);
} catch (Exception e) {
// Ignore
}
manager.getContainer().getLogger().error
(sm.getString("standardSession.sessionEvent"), t);
}
}
}
如果监听为对Session的监听,则触发监听,而在创建session的设置session的id方法中会调用tellNew(),其会触发session创建时session监听程序。
就这么多吧,要上班了。。。