关键类或接口的作用如下:
Manager - 管理 Session 池,不同的实现提供特定的功能,如持久化和分布式
ManagerBase - 实现了一些基本功能,如 Session 池,唯一ID生成算法,便于继承扩展
StandardManager - 标准实现,可在此组件重新启动时提供简单的会话持久性(例如,当整个服务器关闭并重新启动时,或重新加载特定Web应用程序时)
PersistentManagerBase - 提供多种不同的持久化存储管理方式,如文件和数据库
Store - 提供持久化存储和加载会话和用户信息
ClusterManager - 集群 session 管理接口,负责会话的复制方式
DeltaManager - 将会话数据增量复制到集群中的所有成员
BackupManager - 将数据只复制到一个备份节点,集群中所有成员可看到这个节点
本文不分析集群复制的原理,只分析单机 Session 的管理。
3.1 创建 Session在 Servlet 中首次使用 Request.getSession() 获取会话对象时,会创建一个 StandardSession 实例:
public Session createSession(String sessionId) { // 默认返回的是 new StandardSession(this) 实例 Session session = createEmptySession(); // 初始化属性 session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); // 设置会话有效时间,单位 秒,默认 30 分钟,为负值表示永不过期 session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60); if (sessionId == null) { // 生成一个会话 ID sessionId = generateSessionId(); session.setId(sessionId); sessionCounter++; SessionTiming timing = new SessionTiming(session.getCreationTime(), 0); synchronized (sessionCreationTiming) { sessionCreationTiming.add(timing); sessionCreationTiming.poll(); } return (session); }关键就在于会话唯一标识的生成,来看 Tomcat 的生成算法:
随机获取 16 个字节
使用 MD5 加密这些字节,再次得到一个 16 字节的数组
遍历新的字节数组,使用每个字节的高低4位分别生成一个十六进制字符
最后得到一个 32 位的十六进制字符串
核心代码如下:
protected String generateSessionId() { byte random[] = new byte[16]; String jvmRoute = getJvmRoute(); String result = null; // 将结果渲染为十六进制数字的字符串 StringBuffer buffer = new StringBuffer(); do { int resultLenBytes = 0; if (result != null) { // 重复,重新生成 buffer = new StringBuffer(); duplicates++; } // sessionIdLength 为 16 while (resultLenBytes < this.sessionIdLength) { getRandomBytes(random);// 随机获取 16 个字节 // 获取这16个字节的摘要,默认使用 MD5 random = getDigest().digest(random); // 遍历这个字节数组,最后生成一个32位的十六进制字符串 for (int j = 0; j < random.length && resultLenBytes < this.sessionIdLength; j++) { // 使用指定字节的高低4位分别生成一个十六进制字符 byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); // 转为十六进制数字字符 if (b1 < 10) {buffer.append((char) (\'0\' + b1));} // 转为大写的十六进制字符 else {buffer.append((char) (\'A\' + (b1 - 10)));} if (b2 < 10) {buffer.append((char) (\'0\' + b2));} else {buffer.append((char) (\'A\' + (b2 - 10)));} resultLenBytes++; } } if (jvmRoute != null) {buffer.append(\'.\').append(jvmRoute);} result = buffer.toString(); } while (sessions.containsKey(result)); return (result); } 3.2 Session 过期检查