一、前言
? ? ? ?首先介紹下分布式session共享管理
????????在Springboot項目中,經常提到分布式的概念,當實際部署應用后,多臺服務器各自存儲用戶登錄會話無法共享,導致操作A按鈕還是正常,操作B按鈕就提示登錄過期需要重新登錄。
? ? ? ? 這是因為接口分發將請求的sessionID傳入了不同服務器,未使用分布式session管理時只能在其中一臺服務器記錄登錄信息,所以另一臺機器收到請求sessionID識別為未登錄出現異常。
? ? ? ? 所以,此時需要將用戶會話信息存儲在中間件中,不依賴特定服務器,這樣就可以實現各服務器之間共享讀取中間件中用戶的會話,不會出現上述問題
二、問題
? ? ? ? 監聽session情況,創建、銷毀進行回調代碼如下
@Configuration
@Slf4j(topic = "login")
public class SpringSessionConfiguration implements HttpSessionListener {@Overridepublic void sessionCreated(HttpSessionEvent se) {log.info("Session 創建: " + se.getSession().getId());}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {log.info("Session 銷毀: " + se.getSession().getId());}}
通過debug打斷點發現,?不觸發根本原因?:
session失效進入了SessionRepositoryFilter類,沒有進入StandardSessionFacade導致的
我們來詳細分析這個問題的 原因 和 解決方案。
🧠 一、問題現象
- 你實現了?
HttpSessionListener
,并注冊為 Spring Bean。 sessionCreated()
?方法可以正常觸發。- 但?
sessionDestroyed()
?方法不執行,即使 Session 已經過期或被刪除。
🔍 二、根本原因分析
1. Spring Session 的 Session 生命周期管理機制
Spring Session(尤其是 spring-session-data-redis
)是通過 SessionRepositoryFilter
來攔截請求并管理 Session 生命周期的。
- 當用戶訪問時,
SessionRepositoryFilter
?會創建一個包裝過的?HttpSession
(通常是?MapSession
?或?RedisSession
)。 - 這個包裝過的?
HttpSession
?并不是原生的 Tomcat?StandardSessionFacade
,而是 Spring 自己實現的抽象層。
2.?sessionDestroyed()
?不觸發的原因
HttpSessionListener.sessionDestroyed()
是 由 Servlet 容器(如 Tomcat)觸發的,只有當 Session 是由 Tomcat 管理的原生 Session 時才會觸發。
而使用 Spring Session 后:
- Session 是由 Spring Session 管理的,不是由 Tomcat 創建的。
- Session 的銷毀是通過 Spring Session 內部機制完成的(比如 Redis Key 過期、主動調用?
session.invalidate()
)。 - Tomcat 不知道 Session 被銷毀了,因此不會觸發?
sessionDestroyed()
?回調。
3. Session 過期后去哪了?
Spring Session 的 Session 過期通常有以下幾種方式:
- Redis 中 Session Key 的 TTL 到期,被 Redis 自動刪除(被動過期)。
- Spring Session 內部定時任務或請求時檢查過期(主動過期)。
- 但這些方式都不會觸發?
HttpSessionListener.sessionDestroyed()
,因為不是 Tomcat 觸發的。
三、方案
? 方案一:監聽 Spring Session 的?SessionDestroyedEvent
Spring Session 提供了它自己的事件機制,你可以監聽 SessionDestroyedEvent
來替代 HttpSessionListener
。
@Component
@Slf4j
public class SessionEventListener {@EventListenerpublic void handleSessionCreated(SessionCreatedEvent event) {log.info("Session 創建: " + event.getSession().getId());}@EventListenerpublic void handleSessionDestroyed(SessionDestroyedEvent event) {log.info("Session 銷毀: " + event.getSessionId());}
}
? 方案二:啟用 Redis Key 過期事件 + 監聽 Redis 的?expired
?事件
如果你的 Session 是被 Redis 主動刪除的(TTL 過期),Spring Session 默認不會感知到。
你需要:
配置文件中搜索開啟,重啟后生效?
notify-keyspace-events Ex
四、總結
? ? ? ? 經過筆者測試使用方案二實現最終效果,當會話過期自動調用銷毀方法,整個過程還是比較費勁,一邊結合AI分析,一邊查閱資料自己打斷點推敲,事實證明完全依賴AI無法解決實際問題,還需要人腦參與統籌分析,也勸各位讀者不要過分依賴AI。