当前位置: 首页 > news >正文

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 一样,这个动作也是具有传递性的,也就是说子容器还会把这个动作传递给自己的子容器。

image-20241119092240111

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 的清理肯定也有

image-20241119093125898

查看 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:

image-20241119093711678

curl localhost:8080/path/test/111

调用栈如图:

image-20241119093801083

解释下调用栈:

  • 我们拿到的是一个 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 创建完成的通知

  • image-20241119110406918

    另外,我们拿到的 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);}

报错信息:

image-20241119111035583

参考资料

【1】https://zhuanlan.zhihu.com/p/138791035

【2】https://time.geekbang.org/column/article/109635

相关文章:

Tomcat 如何管理 Session

Tomcat 如何管理 Session 我们知道&#xff0c;Tomcat 中每一个 Context 容器对应一个 Web 应用&#xff0c;而 Web 应用之间的 Session 应该是独立的&#xff0c;因此 Session 的管理肯定是 Context 级的&#xff0c;也就是一个 Context 一定关联多个 Session。 Tomcat 中主…...

stm32启动过程解析startup启动文件

1.STM32的启动过程模式 1.1 根据boot引脚决定三种启动模式 复位后&#xff0c;在 SYSCLK 的第四个上升沿锁存 BOOT 引脚的值。BOOT0 为专用引脚&#xff0c;而 BOOT1 则与 GPIO 引脚共用。一旦完成对 BOOT1 的采样&#xff0c;相应 GPIO 引脚即进入空闲状态&#xff0c;可用于…...

SystemVerilog学习——构造函数new

一、概述 在 SystemVerilog 中&#xff0c;new 是一个构造函数&#xff0c;用于创建类的实例&#xff08;即对象&#xff09;。它在面向对象编程&#xff08;OOP&#xff09;中起着重要作用&#xff0c;负责实例化一个对象并进行初始化。与传统编程语言&#xff08;如 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 集合框架&#xff08;Collections Framework&#xff09;1.2 输入输出流&#xff08;I/O Streams&#xff09;1.3 并发编程&#xff08;Concurrency&#xff09;1.4 反射&#xff08;Reflection&#xff09;1.5 泛型&…...

esp32c3开发板通过micropython的ubluetooth库连蓝牙设备

ESP32-C3开发板是一款高性能、低功耗的微控制器&#xff0c;搭载了Espressif自家的RISC-V处理器。通过MicroPython&#xff0c;一种面向微控制器的精简版Python编程语言&#xff0c;开发者可以轻松地为ESP32-C3编写代码。MicroPython的ubluetooth库使得ESP32-C3能够通过蓝牙与各…...

leetcode hot100【LeetCode 35.搜索插入位置】java实现

LeetCode 35.搜索插入位置 题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用 O(log n) 的时间复杂度来实现。 示例 1: 输入: nums [1,3,5,6…...

我们要用平凡来诠释非凡

#孟晚舟香港中文大学演讲# #华为价值观念# #并非站在山顶才能被看见# #传递正确的价值观# #如果信仰有颜色&#xff0c;那一定是中国红# #送给自己的价值理念# 在信息大爆炸的时代&#xff0c;很多同学都希望尽可能的抓取更多的知识&#xff0c;尽可能的不要遗漏任何热点…...

synchronized和volatile区别

synchronized和volatile是Java并发编程中两种重要的同步机制&#xff0c;它们之间存在明显的区别。以下是对这两者的详细比较&#xff1a; 一、基本定义与作用 synchronized 是一个用于实现线程同步的关键字。可以用来锁住方法或代码块&#xff0c;从而确保在同一时刻只有一个…...

125.验证回文串-力扣(LeetCode)

题目&#xff1a; 解题思路&#xff1a; 首先进行移除非字母数字字符&#xff0c;并将大写字符转换为小写字符的操作。这个过程中&#xff0c;主要利用快慢指针的方式来进行移除操作&#xff0c;通过加32将大写字符转换为小写字符。完成后&#xff0c;将前一半的数据与后一半的…...

线程间通信:wait和notify

线程间通信&#xff1a;wait和notify 1、Object的wait和notify方法 Java中的Object类提供了两个重要的方法&#xff0c;用于线程间的通信和同步&#xff1a;wait()方法和notify()方法 wait()方法的定义 方法签名&#xff1a;public final void wait() throws InterruptedEx…...

风险识别和管理的工具

1.‌风险识别工具和根本原因识别在项目管理中非常重要&#xff0c;常用的工具包括 因果图根本原因识别RCA鱼骨图 因果图 因果图是一种图形工具&#xff0c;用于识别问题或风险的根本原因。它通过将问题或风险因素与可能的根本原因联系起来&#xff0c;帮助团队更深入地了解问…...

qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能

一、前言 主要功能如下&#xff1a; 1.实现文件夹的下载和删除&#xff0c;网上很多资料都是单独对某个路径的文件操作的&#xff0c;并不能对文件夹操作 2.实现目标机中含中文名称自动转码&#xff0c;有些系统编码方式不同&#xff0c;下载出来的文件会乱码 3.实现ftp功能…...

为何数据库推荐将IPv4地址存储为32位整数而非字符串?

目录 一、IPv4地址在数据库中的存储方式&#xff1f; 二、IPv4地址的存储方式比较 &#xff08;一&#xff09;字符串存储 vs 整数存储 &#xff08;二&#xff09;IPv4地址"192.168.1.8"说明 三、数据库推荐32位整数存储方式原理 四、存储方式对系统性能的影响…...

Mybatis框架之责任链模式 (Chain of Responsibility Pattern)

在 MyBatis 框架中&#xff0c;责任链模式 (Chain of Responsibility Pattern) 被广泛应用于多个功能模块中&#xff0c;例如 插件拦截器、SQL 执行流程中的拦截器链、动态 SQL 的解析与处理等。这种设计模式为 MyBatis 提供了高度的扩展性和灵活性&#xff0c;使其能够轻松应对…...

C++ Stack和Queue---单向守护与无尽等待:数据结构的诗意表达

公主请阅 容器适配器容器适配器的特点 栈和队列的模拟实现deque的介绍1. 内存开销较高2.随机访问性能略低于 vector3. 与指针或迭代器的兼容性r4. 不适合用于需要频繁中间插入和删除的场景5. 在特定平台上的实现不一致6. 缺乏shrink_to_fit支持总结 题目 priority_queue 优先级…...

深入理解Java包装类与泛型的应用

今天我将带领大家进入Java包装类和泛型应用的学习。 我的个人主页 我的Java-数据结构专栏 &#xff1a;Java-数据结构&#xff0c;希望能帮助到大家。 一、Java包装类基础 二、Java泛型基础 三、Java包装类与泛型的结合 四、Java泛型进阶 五、Java包装类与泛型实战 一、Ja…...

【机器学习chp4】特征工程

推荐文章1&#xff0c;其中详细分析了为什么L1正则化可以实现特征选择&#xff08;特征剔除&#xff09; 【王木头 L1、L2正则化】三个角度理解L1、L2正则化的本质-CSDN博客 推荐文章2&#xff0c;里面详细分析了奇异值分解 【线性代数】矩阵变换-CSDN博客 本文遗留问题&#…...

LeetCode螺旋矩阵

快一个月没刷题了&#xff0c;最近工作有些忙&#xff0c;今天闲下来两小时&#xff0c;刷一道 题目描述 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4…...

第十五届蓝桥杯JAVA的B组题目详情解析

(第一个填空太简单&#xff0c;就不写了,根本不用代码&#xff0c;直接excel计算) 目录 蓝桥杯第二个填空&#xff0c;类斐波那契循环数 蓝桥杯JAVA.b组第三题 -分布式队列(模拟) 食堂(蓝桥杯D题) ​编辑 星际旅行(Floyd佛洛依德) 其余的有点变态&#xff0c;感觉学了好像…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

LangFlow技术架构分析

&#x1f527; LangFlow 的可视化技术栈 前端节点编辑器 底层框架&#xff1a;基于 &#xff08;一个现代化的 React 节点绘图库&#xff09; 功能&#xff1a; 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...