Tomcat 如何管理 Session
Tomcat 如何管理 Session
我们知道,Tomcat 中每一个 Context 容器对应一个 Web 应用,而 Web 应用之间的 Session 应该是独立的,因此 Session 的管理肯定是 Context 级的,也就是一个 Context 一定关联多个 Session。
Tomcat 中主要由每个 Context 容器内的一个 Manager 对象来管理 Session。抽象实现类是 ManagerBase,默认实现类为 StandardManager。
查看 org.apache.catalina.Manager 的接口(省略部分):
public interface Manager {public Context getContext();public void setContext(Context context);public void add(Session session);public void changeSessionId(Session session);public void changeSessionId(Session session, String newId);public Session createEmptySession();public Session createSession(String sessionId);public Session findSession(String id) throws IOException;public Session[] findSessions();public void load() throws ClassNotFoundException, IOException;public void remove(Session session);public void remove(Session session, boolean update);public void unload() throws IOException;public void backgroundProcess();
}
其中就有创建和删除 Session 的接口,其中的 load 和 reload 是将 session 在存储介质中 保存/卸载。
Session 的创建
查看 Manager 的抽象实现类 org.apache.catalina.session.ManagerBase
public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {/*** The Context with which this Manager is associated.*/private Context context;/*** The set of currently active Sessions for this Manager, keyed by* session identifier.*/protected Map<String, Session> sessions = new ConcurrentHashMap<>();}
其中有两个关键属性如上所示:一个是与该对象关联的 Context 对象,一个是存放 session 的一个 Map。
StandardManager 继承了 ManagerBase,直接复用了它的创建方法
public Session createSession(String sessionId) {// 首先判断 Session 数量是不是到了最大值,最大 Session 数可以通过参数设置if ((maxActiveSessions >= 0) &&(getActiveSessions() >= maxActiveSessions)) {rejectedSessions++;throw new TooManyActiveSessionsException(sm.getString("managerBase.createSession.ise"),maxActiveSessions);}// 复用或者创建一个 session 实例Session session = createEmptySession();// 初始化空 session 的属性值session.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);String id = sessionId;if (id == null) {id = generateSessionId();}// setId 将 session 保存到了 map 中session.setId(id);// 计数器加 1sessionCounter++;SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);synchronized (sessionCreationTiming) {sessionCreationTiming.add(timing);sessionCreationTiming.poll();}return session;}
查看 setId() 方法
public void setId(String id, boolean notify) {if ((this.id != null) && (manager != null)) {manager.remove(this);}this.id = id;if (manager != null) {// 实现类里把 session put 到了 map 里manager.add(this);}// 如果需要触发创建通知if (notify) {tellNew();}}/*** 通知监听器有关新会话的信息*/public void tellNew() {// Notify interested session event listenersfireSessionEvent(Session.SESSION_CREATED_EVENT, null);// 获取 context 对象和 listener 对象Context context = manager.getContext();Object listeners[] = context.getApplicationLifecycleListeners();if (listeners != null && listeners.length > 0) {HttpSessionEvent event =new HttpSessionEvent(getSession());for (Object o : listeners) {// 判断是否是 HttpSessionListener 的实例if (!(o instanceof HttpSessionListener)) {continue;}HttpSessionListener listener = (HttpSessionListener) o;try {context.fireContainerEvent("beforeSessionCreated", listener);// 其实就是这个方法,我们只需要实现 HttpSessionListener 并注册 bean 就可以listener.sessionCreated(event);context.fireContainerEvent("afterSessionCreated", listener);} catch (Throwable t) {ExceptionUtils.handleThrowable(t);try {context.fireContainerEvent("afterSessionCreated", listener);} catch (Exception e) {// Ignore}manager.getContext().getLogger().error (sm.getString("standardSession.sessionEvent"), t);}}}}
Session 的清理
容器组件会开启一个 ContainerBackgroundProcessor 后台线程,调用自己以及子容器的 backgroundProcess 进行一些后台逻辑的处理,和 Lifecycle 一样,这个动作也是具有传递性的,也就是说子容器还会把这个动作传递给自己的子容器。

StandardContext 重写了该方法,它会调用 StandardManager 的 backgroundProcess 进而完成 Session 的清理工作,下面是 StandardManager 的 backgroundProcess 方法的代码:
public void backgroundProcess() {// processExpiresFrequency 默认值为 6,而 backgroundProcess 默认每隔 10s 调用一次,也就是说除了任务执行的耗时,每隔 60s 执行一次count = (count + 1) % processExpiresFrequency;if (count == 0) // 默认每隔 60s 执行一次 Session 清理processExpires();
}/*** 单线程处理,不存在线程安全问题*/
public void processExpires() {// 获取所有的 SessionSession sessions[] = findSessions(); int expireHere = 0 ;for (int i = 0; i < sessions.length; i++) {// Session 的过期是在 isValid() 方法里处理的if (sessions[i]!=null && !sessions[i].isValid()) {expireHere++;}}
}
既然 session 的创建会有事件通知,那么 session 的清理肯定也有

查看 HttpSessionListener:
public interface HttpSessionListener extends EventListener {/*** Notification that a session was created.* The default implementation is a NO-OP.** @param se* the notification event*/public default void sessionCreated(HttpSessionEvent se) {}/*** Notification that a session is about to be invalidated.* The default implementation is a NO-OP.** @param se* the notification event*/public default void sessionDestroyed(HttpSessionEvent se) {}
}
这两个方法分别在 session 创建和销毁时被调用
实践
@RestController
@RequestMapping("/path")
@Slf4j
public class PathController {@GetMapping("/test/{strs}")public void testPath(@PathVariable("strs") String strs, HttpServletRequest request) {HttpSession session = request.getSession();log.info("接收到的strs值为:{}", strs);}}
@Slf4j
@Configuration
public class SessionListener implements HttpSessionListener {@Overridepublic void sessionCreated(HttpSessionEvent se) {log.info("session created");}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {log.info("session destroyed");}
}
进行 Debug:

curl localhost:8080/path/test/111
调用栈如图:

解释下调用栈:
-
我们拿到的是一个 RequestFacade 类的对象,这个对象其实是真正 request 的包装类,构造器如下:
-
public RequestFacade(Request request) {this.request = request;} -
在包装类里,我们调用了内部的 getSession(),省略非关键代码,最后调用了内部 request 的 getSession
-
public HttpSession getSession() {return getSession(true);} -
public HttpSession getSession(boolean create) {if (SecurityUtil.isPackageProtectionEnabled()){return AccessController.doPrivileged(new GetSessionPrivilegedAction(create));} else {// 调用了内部 request 的 getSessionreturn request.getSession(create);}} -
在 request 的 getSession 中,取到了 context 和 manager 对象,开始走目录1中 session 创建的逻辑
-
protected Session doGetSession(boolean create) {// There cannot be a session if no context has been assigned yetContext context = getContext();// Return the requested session if it exists and is validManager manager = context.getManager(); -
// Create a new session if requested and the response is not committedif (!create) {return null;}// Re-use session IDs provided by the client in very limited// circumstances.String sessionId = getRequestedSessionId();// 这个方法走到了 ManagerBase 的 createSession ,也就是目录1中介绍过的函数session = manager.createSession(sessionId);session.access();return session;} -
最终进行 session 创建完成的通知
-

另外,我们拿到的 session 其实也是一个包装类,包装类的好处是对外界封闭细节,避免暴露过多的内部实现,防止外界进行危险操作,例如这里我们假如把 session 强转为 StandardSession,就会出现类型转换的错误。如下:
@GetMapping("/test/{strs}")public void testPath(@PathVariable("strs") String strs, HttpServletRequest request) {HttpSession session = request.getSession();// 强转为 StandardSessionStandardSession standardSession = (StandardSession) session;// 拿到 manager 对象,多造几个 sessionManager manager = standardSession.getManager();manager.createSession("1111");manager.createSession("2222");log.info("接收到的strs值为:{}", strs);}
报错信息:

参考资料
【1】https://zhuanlan.zhihu.com/p/138791035
【2】https://time.geekbang.org/column/article/109635
相关文章:
Tomcat 如何管理 Session
Tomcat 如何管理 Session 我们知道,Tomcat 中每一个 Context 容器对应一个 Web 应用,而 Web 应用之间的 Session 应该是独立的,因此 Session 的管理肯定是 Context 级的,也就是一个 Context 一定关联多个 Session。 Tomcat 中主…...
stm32启动过程解析startup启动文件
1.STM32的启动过程模式 1.1 根据boot引脚决定三种启动模式 复位后,在 SYSCLK 的第四个上升沿锁存 BOOT 引脚的值。BOOT0 为专用引脚,而 BOOT1 则与 GPIO 引脚共用。一旦完成对 BOOT1 的采样,相应 GPIO 引脚即进入空闲状态,可用于…...
SystemVerilog学习——构造函数new
一、概述 在 SystemVerilog 中,new 是一个构造函数,用于创建类的实例(即对象)。它在面向对象编程(OOP)中起着重要作用,负责实例化一个对象并进行初始化。与传统编程语言(如 C 或 Jav…...
力扣题目总结
1.游戏玩法分析IV AC: select IFNULL(round(count(distinct(Result.player_id)) / count(distinct(Activity.player_id)), 2), 0) as fraction from (select Activity.player_id as player_idfrom (select player_id, DATE_ADD(MIN(event_date), INTERVAL 1 DAY) as second_da…...
Java API 进阶指南:从核心API到高级应用的全面提升
文章目录 Java API 进阶学习指南1. 深入理解核心API1.1 集合框架(Collections Framework)1.2 输入输出流(I/O Streams)1.3 并发编程(Concurrency)1.4 反射(Reflection)1.5 泛型&…...
esp32c3开发板通过micropython的ubluetooth库连蓝牙设备
ESP32-C3开发板是一款高性能、低功耗的微控制器,搭载了Espressif自家的RISC-V处理器。通过MicroPython,一种面向微控制器的精简版Python编程语言,开发者可以轻松地为ESP32-C3编写代码。MicroPython的ubluetooth库使得ESP32-C3能够通过蓝牙与各…...
leetcode hot100【LeetCode 35.搜索插入位置】java实现
LeetCode 35.搜索插入位置 题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用 O(log n) 的时间复杂度来实现。 示例 1: 输入: nums [1,3,5,6…...
我们要用平凡来诠释非凡
#孟晚舟香港中文大学演讲# #华为价值观念# #并非站在山顶才能被看见# #传递正确的价值观# #如果信仰有颜色,那一定是中国红# #送给自己的价值理念# 在信息大爆炸的时代,很多同学都希望尽可能的抓取更多的知识,尽可能的不要遗漏任何热点…...
synchronized和volatile区别
synchronized和volatile是Java并发编程中两种重要的同步机制,它们之间存在明显的区别。以下是对这两者的详细比较: 一、基本定义与作用 synchronized 是一个用于实现线程同步的关键字。可以用来锁住方法或代码块,从而确保在同一时刻只有一个…...
125.验证回文串-力扣(LeetCode)
题目: 解题思路: 首先进行移除非字母数字字符,并将大写字符转换为小写字符的操作。这个过程中,主要利用快慢指针的方式来进行移除操作,通过加32将大写字符转换为小写字符。完成后,将前一半的数据与后一半的…...
线程间通信:wait和notify
线程间通信:wait和notify 1、Object的wait和notify方法 Java中的Object类提供了两个重要的方法,用于线程间的通信和同步:wait()方法和notify()方法 wait()方法的定义 方法签名:public final void wait() throws InterruptedEx…...
风险识别和管理的工具
1.风险识别工具和根本原因识别在项目管理中非常重要,常用的工具包括 因果图根本原因识别RCA鱼骨图 因果图 因果图是一种图形工具,用于识别问题或风险的根本原因。它通过将问题或风险因素与可能的根本原因联系起来,帮助团队更深入地了解问…...
qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能
一、前言 主要功能如下: 1.实现文件夹的下载和删除,网上很多资料都是单独对某个路径的文件操作的,并不能对文件夹操作 2.实现目标机中含中文名称自动转码,有些系统编码方式不同,下载出来的文件会乱码 3.实现ftp功能…...
为何数据库推荐将IPv4地址存储为32位整数而非字符串?
目录 一、IPv4地址在数据库中的存储方式? 二、IPv4地址的存储方式比较 (一)字符串存储 vs 整数存储 (二)IPv4地址"192.168.1.8"说明 三、数据库推荐32位整数存储方式原理 四、存储方式对系统性能的影响…...
Mybatis框架之责任链模式 (Chain of Responsibility Pattern)
在 MyBatis 框架中,责任链模式 (Chain of Responsibility Pattern) 被广泛应用于多个功能模块中,例如 插件拦截器、SQL 执行流程中的拦截器链、动态 SQL 的解析与处理等。这种设计模式为 MyBatis 提供了高度的扩展性和灵活性,使其能够轻松应对…...
C++ Stack和Queue---单向守护与无尽等待:数据结构的诗意表达
公主请阅 容器适配器容器适配器的特点 栈和队列的模拟实现deque的介绍1. 内存开销较高2.随机访问性能略低于 vector3. 与指针或迭代器的兼容性r4. 不适合用于需要频繁中间插入和删除的场景5. 在特定平台上的实现不一致6. 缺乏shrink_to_fit支持总结 题目 priority_queue 优先级…...
深入理解Java包装类与泛型的应用
今天我将带领大家进入Java包装类和泛型应用的学习。 我的个人主页 我的Java-数据结构专栏 :Java-数据结构,希望能帮助到大家。 一、Java包装类基础 二、Java泛型基础 三、Java包装类与泛型的结合 四、Java泛型进阶 五、Java包装类与泛型实战 一、Ja…...
【机器学习chp4】特征工程
推荐文章1,其中详细分析了为什么L1正则化可以实现特征选择(特征剔除) 【王木头 L1、L2正则化】三个角度理解L1、L2正则化的本质-CSDN博客 推荐文章2,里面详细分析了奇异值分解 【线性代数】矩阵变换-CSDN博客 本文遗留问题&#…...
LeetCode螺旋矩阵
快一个月没刷题了,最近工作有些忙,今天闲下来两小时,刷一道 题目描述 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。 示例 1: 输入:matrix [[1,2,3],[4…...
第十五届蓝桥杯JAVA的B组题目详情解析
(第一个填空太简单,就不写了,根本不用代码,直接excel计算) 目录 蓝桥杯第二个填空,类斐波那契循环数 蓝桥杯JAVA.b组第三题 -分布式队列(模拟) 食堂(蓝桥杯D题) 编辑 星际旅行(Floyd佛洛依德) 其余的有点变态,感觉学了好像…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
