当前位置: 首页 > 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;感觉学了好像…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...