从零开始手写Tomcat的教程9节---Session管理
- Session管理
- Session对象
- Session接口
- StandardSession对象
- StandardSessionFacade类
- Manager
- Manager接口
- ManagerBase类
- StandardManager类
- PersistentManagerBase类
- PersistentManager类
- DistributedManaeger类
- 存储器
- storeBase类
- FileStore类
- JDBCStore类
- Session对象
- 应用程序
- SimpleWrapperValve类
- 小结
本系列专题针对Tomcat 4.0.12来进行书写的
4.0.12源码包下载
Session管理
顶层的Manager接口
代码语言:javascript复制 /**
* Return the session associated with this Request, creating one
* if necessary and requested.
*
* @param create Create a new session if one does not exist
*/
public HttpSession getSession(boolean create) {
//如果存在安全管理器,则验证是否有权限获得session对象
if( System.getSecurityManager() != null ) {
PrivilegedGetSession dp = new PrivilegedGetSession(create);
return (HttpSession)AccessController.doPrivileged(dp);
}
return doGetSession(create);
}
doGetSession方法实现:
代码语言:javascript复制 private HttpSession doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null)
return (null);
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session.getSession());
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)
//从当前request关联的Context容器中获得session管理器对象
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
//通过我们熟知的sessionId去查找具体的session对象
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
//校验session状态是否正确
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
return (session.getSession());
}
}
// Create a new session if requested and the response is not committed
//走到这里说明是一次获取session,或者session失效了,如果create为真,需要重写创建一个
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("httpRequestBase.createCommitted"));
}
//session管理器复制新创建一个session对象
session = manager.createSession();
if (session != null)
return (session.getSession());
else
return (null);
}
特别注意写了中文注释的地方,其余英文注释为源码自带的英文注释,总体来说这段代码还算比较简单易懂
剧透:manage是一个接口,负责管理session对象,当然也就包括session存储方式,因此不同的manager实现类,可以完成多种session存储机制,例如:存放在内存或者数据库或者文件中
Session对象
这里StandardSessionFacade外观类的使用是否让大家联想到了前面servlet.service(httpRequestFacade,httpResponseFacade)方法的调用呢?
本质目的都是一样的,都是为了防止用户做强制类型转换,调用内部方法,造成系统隐患
这里内部session接口的使用,个人认为目的是主要是用来扩展原本的sevlet规范中HttpSession给出的功能,提供诸如监听器,session管理器的功能,在session接口中
Session接口
- 关联某个Context实例
- 设置session在Context实例中的唯一标识
- 通过最后一次访问时间和当前时间做差,判断时间差值是否超过session过期时间,来决定session是否过期
- getSession返回的是Session包装的HttpSession外观类对象
StandardSession对象
Session管理器负责管理session集合,同时每个session对象还需要保存当前所属的管理器对象
我们还可以注意一下,很多组件都维护一个监听器列表,这个细节虽然有时候看起来没啥用,但是真的等到你需要在当前框架基础上进行一些扩展时,这个监听器往往可以给系统提供很好的扩展性和弹性伸缩空间。
- StandardSession中的getSession方法,返回给客户端的是通过统一抽象层HttpSession形式返回,并且为了防止用户强制类型转换调用Session或者当前类StandardSession中某些特殊方法,返回给客户端的是与HttpSession同级别的StandardSessionFacade外观对象
强烈建议大家学习一下这种外观模式的思想
- 监听器机制,这里tomcat实现了一种多级多级监听器的实例,不仅有细化到一个session实例对象上保存的监听器,还有整个应用程序级别的监听器。并且还将一个session过期事件,细分到过期前,过期中,过期后进行三步通知。
StandardSessionFacade类
我上面说的不够详细,这里原因讲的非常清楚。
Manager
Manager接口
这里我有一点疑惑:
之前说过tomcat中的container接口是提供给每个容器对应管道中阀是否需要拥有当前对应的容器实例对象时,可以继承的接口
为什么这里当其他接口或类需要容器实例对象时,不能直接继承该接口,还要重复书写。
ManagerBase类
StandardManager类
代码语言:javascript复制 /**
* Set the Manager with which this Container is associated.
*
* @param manager The newly associated Manager
*/
public synchronized void setManager(Manager manager) {
// Change components if necessary
Manager oldManager = this.manager;
if (oldManager == manager)
return;
this.manager = manager;
// Stop the old component if necessary
if (started && (oldManager != null) &&
(oldManager instanceof Lifecycle)) {
try {
((Lifecycle) oldManager).stop();
} catch (LifecycleException e) {
log("ContainerBase.setManager: stop: ", e);
}
}
// Start the new component if necessary
if (manager != null)
//设置manager和某个容器相关联
manager.setContainer(this);
if (started && (manager != null) &&
(manager instanceof Lifecycle)) {
try {
((Lifecycle) manager).start();
} catch (LifecycleException e) {
log("ContainerBase.setManager: start: ", e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("manager", oldManager, this.manager);
}
列出这段代码,主要想说明Container容器调用setManager方法设置容器和session管理器相关联,而在此方法中又调用manager.setContainer将管理器和容器相关联,形成一种双向关联机制
- ContainerBase是啥? —完全没听过的建议回顾我之前讲过的servlet容器章节
从零开始手写Tomcat的教程5节—servlet容器
ContainerBase是所有具体容器实现的抽象父类,例如StanardEngine,StandardHost,StandardContext等
- backgroudProcess方法之前遇到过吧?
从零开始手写Tomcat的教程8节----加载器
在本节中,Loader实现自动重载功能,也是通过在该方法中不断轮询完成的,当然指的都是Tomcat5。
PersistentManagerBase类
具体换出流程,大家可以仔细看一下
默认为-1表示不会被备份或者换出
不要走入误区: 即将过期的session会进行备份和换出
过期检查在备份和换出检查之前,表明在进行备份和换出检查时,过期的session一定是被过滤掉的,因此这里空闲时间如果设置的比session过期时间都大,将毫无意义
这里官方的意图针对的是有大量session同时需要创建保存在内存上时,会对内存产生很大的压力,此时我们通过设置一个比较小的空闲时间,可以确保及时将一部分session从内存转储到文件中,从而减轻内存压力
代码语言:javascript复制 /**
* Invalidate all sessions that have expired.
*/
protected void processExpires() {
if (!started)
return;
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
for (int i = 0; i < sessions.length; i ) {
StandardSession session = (StandardSession) sessions[i];
if (!session.isValid())
continue;
if (isSessionStale(session, timeNow))
session.expire();
}
}
检查session是否过期,会从内存和存储文件两方面进行检查,因此不用担心被转存到文件的session不会被检查是否过期的问题
PersistentManager类
DistributedManaeger类
存储器
storeBase类
存储器类是给PersistentManagerBase的实现类使用的