前段时间看了阵子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监听程序。
就这么多吧,要上班了。。。
分享到:
相关推荐
tomcat-redis-session-manager源码
tomcat 集群 nginx 使用redis 保证session同步
Tomcat8亲测可用 tomcat-redis-session-manager的jar包 修改了tomcat-redis-session-manager源码进行的编译生成的jar包
这个包里含有commons-pool2-2.4.2、jedis-2.9.0、tomcat85-session-redis-1.0三个主要JAR包。apache-tomcat-8.5.20.tar.gz源码包和context.xml文件,这套配置是我自己亲测可用的。。另外我用的redis4这个版本。注意...
因tomcat7使用redis共享session,其他的包存在问题,自己编译后处理通过。 该包是在https://github.com/jcoleman/tomcat-redis-session-manager 将源码编译后的包。
很好用,使用也很简单,把其中的三个Jar包拷贝到你的tomcat中的...tomcat8、8.5、9与redis实现session共享,并可以通过修改源码可自定义session键,访问地址:http://blog.csdn.net/fackyou200/article/details/78929008
基于tomcat-redis-session-manager源码进行的编译生成的jar包,压缩包中包含Tomcat7和Tomcat8打好的jar包。
NULL 博文链接:https://mukeliang.iteye.com/blog/2197850
tomcat session共享源码学习
redis+tomcat实现session共享需要的包,源码编译打包生成,亲测可用. 编译环境:tomcat7+jdk7。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
NULL 博文链接:https://fanshuyao.iteye.com/blog/2400975
memcached-session-manager-tc7.jar实现Tomcat+redis session共享, 解压该文件,将里面的jar包丢Tomcat7目录lib下进行相关配置即可(配置网上有),由官方git地址:...
tomcat8下 tomcat-redis-session-manager , github上有源码,其他版本都有打好的jar包,tomcat 8 下没有,下载源码生成了一个。
NULL 博文链接:https://rainbow702.iteye.com/blog/1312307
Nginx 集群 tomcat session 共享配置有源码 介绍看博客:https://blog.csdn.net/wxb880114/article/details/80563301
自己通过源码编译后的jar包,已实验可以正常使用
包含Redis-x64-3.2.100.zip,nginx-1.13.1.zip,我自己搭建的spring+redis 实现session共享的源码
NULL 博文链接:https://gjp014.iteye.com/blog/2372273