Shiro原始碼分析(2) - 會話管理器(SessionManager)
本文在於分析Shiro原始碼,對於新學習的朋友可以參考
[開濤部落格](http://jinnianshilongnian.iteye.com/blog/2018398)進行學習。
本文對Shiro中的SessionManager進行分析,SessionManager用於管理Shiro中的Session資訊。Session也就是我們通常說的會話,會話是使用者在使用應用程式一段時間內攜帶的資料。傳統的會話一般是基於Web容器(如:Tomcat、EJB環境等)。Shiro提供的Session可以在任何環境中使用,不再依賴於其他容器。
Shiro還提供了一些其他的特性:
- 基於POJO/J2SE:Session和SessionManager都是基於介面實現的,可以通過POJO來進行實現。可以使用任務JavaBean相容的格式(如:Json,YAML,spring xml或類似機制)來輕鬆配置所有會話元件。能夠根據需要完全地擴充套件會話元件。
- 會話儲存:因為Shiro的Session物件是基於POJO的,所以會話資料可以容易地儲存在任何資料來源中。 這允許您精確定製應用程式的會話資料所在的位置,例如檔案系統,企業快取,關係資料庫或專有資料儲存。
- 簡單強大的叢集:Shiro的Session可以通過快取來實現叢集功能,這樣的Session並不會依賴於Web容器,不需要對Web容器進行特殊的配置。
- 事件監聽:事件監聽器允許在會話生命週期中接受生命週期事件,可以監聽這些事件並對自定義應用程式行為做出反應。例如,在會話過期時更新使用者記錄。
- 主機地址保留:會記錄Session建立時的IP地址。
- 會話過期:會話由於預期的不活動而過期,但如果需要,可以通過touch()方法延長活動以保持活動狀態。
Session介面定義
Shiro提供的Session和Servlet中的Session其實是一樣的作用,只是Shiro中的Sesion不需要再依賴於WEB容器存在。下面是Session介面提供的方法:
/**
* 返回表示Session的唯一ID
*/
Serializable getId();
/**
* 返回Session的開始時間
*/
Date getStartTimestamp();
/**
* 返回最近使用的時間
*/
Date getLastAccessTime();
/**
* 返回還有多久Session過期(毫秒)
*/
long getTimeout() throws InvalidSessionException;
/**
* 設定超時時間(毫秒)
*/
void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
/**
* 返回Session建立時的主機名或地址
*/
String getHost();
/**
* 更新最近訪問時間,確保Session不會過期
*/
void touch() throws InvalidSessionException;
/**
* 設定Session停止使用,並釋放相關資源
*/
void stop() throws InvalidSessionException;
/**
* 返回該Session儲存的所有屬性鍵
*/
Collection<Object> getAttributeKeys() throws InvalidSessionException;
/**
* 獲取Session屬性
*/
Object getAttribute(Object key) throws InvalidSessionException;
/**
* 設定Session屬性
*/
void setAttribute(Object key, Object value) throws InvalidSessionException;
/**
* 刪除Session屬性
*/
Object removeAttribute(Object key) throws InvalidSessionException;
從介面中我們可以看出。Session有一個唯一ID,開始時間,最近活動時間等屬性,另外提供了兩個相對應的方法touch()和stop(),其他方法就是對屬性的操作了。Session介面還是相當簡單清晰的,下面我們看看介面的實現類。
Session有一些實現類,包括SimpleSession、HttpServletSession和DelegatingSession等。SimpleSession是Shiro提供的一種簡單實現,HttpServletSession是基於Sevlet中Session來實現的,DelegatingSession是一種委託機制,委託給SessionManager來實現。下面,我們主要以SimpleSession和DelegatingSession來分析。
SimpleSession實現
SimpleSession從Session介面繼承過來的方法實現非常簡單,就不過多分析了。我們主要分析一下屬性,通過屬性就可以瞭解到SimpleSession中功能的實現了。
// Session唯一ID
private transient Serializable id;
// 開始時間
private transient Date startTimestamp;
// 結束時間
private transient Date stopTimestamp;
// 最近訪問時間
private transient Date lastAccessTime;
// 還有多久過期
private transient long timeout;
// 是否過期
private transient boolean expired;
// Session建立時的主機名
private transient String host;
// Session儲存的屬性值
private transient Map<Object, Object> attributes;
DelegatingSession實現
DelegatingSession是一種委託實現方式,所有的操作都委託給SessionManager介面來實現。我們還是先看有哪些屬性。
// SessionKey就是Session唯一ID的物件形式
private final SessionKey key;
// 委派給NativeSessionManager物件,這是SessionManager介面的一個子介面
private final transient NativeSessionManager sessionManager;
很顯然,DelegatingSession的委託方式是用過SessionKey從SessionManager中獲取相應的Session來進行處理的。在SessionManager中管理著很多Session。
在分析SessionManager前,先說明一下SessionContext這個介面(上一篇中提到過 )。SessionContext表示的是Session建立時的上下文引數,SessionContext有DefaultSessionContext,DefaultWebSessionContext兩個實現。但功能都是從MapContext繼承來,簡單地說SessionContext就是一個Map物件,提供了一個更方便獲取具體型別的方法getTypedValue(String key, Class<E> type)。
SessionManager分析
SessionManager管理著Session的建立、操作以及清除等。SessionManager有一些子介面,包括NativeSessionManager、ValidatingSessionManager和WebSessionManager。每個介面都提供了相關的抽象類AbstractSessionManager、AbstractNativeSessionManager、AbstractValidatingSessionManager。我們還是先看看介面提供了哪些方法。
public interface SessionManager {
/**
* 開始一個新的Session,context提供一些初始化的資料
*/
Session start(SessionContext context);
/**
* 通過SessionKey查詢Session
* 如果存在則被找到,如果不存在則返回null;如果找到的Session無效(被停止或過期)則會拋異常
*/
Session getSession(SessionKey key) throws SessionException;
}
public interface NativeSessionManager extends SessionManager {
/**
* 返回Session被開啟的時間
*/
Date getStartTimestamp(SessionKey key);
/**
* 獲取Session最近訪問的時間
*/
Date getLastAccessTime(SessionKey key);
/**
* 判斷Session是否有效
*/
boolean isValid(SessionKey key);
/**
* 檢測Session是否有效,如果無效則丟擲異常
*/
void checkValid(SessionKey key) throws InvalidSessionException;
/**
* 返回Session還有多久過期(毫秒)
* 如果是負數,表示Session不會過期;如果是正數,表示Session在這個時間後就會過期。
* 如果Session無效呼叫這個方法會拋異常
*/
long getTimeout(SessionKey key) throws InvalidSessionException;
/**
* 設定過期時間(毫秒),設定負數表示Session不會過期。
* 如果Session無效呼叫這個方法會拋異常
*/
void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException;
/**
* 會設定最近訪問時間,確保Session沒有超時,當然,如果Session已經無效也會拋異常
*/
void touch(SessionKey key) throws InvalidSessionException;
/**
* 返回主機名或Ip
*/
String getHost(SessionKey key);
/**
* 停用Session,釋放資源
*/
void stop(SessionKey key) throws InvalidSessionException;
/**
* 返回Session中儲存的所有屬性鍵
*/
Collection<Object> getAttributeKeys(SessionKey sessionKey);
/**
* 獲取Session中的屬性
*/
Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
/**
* 設定Session中的屬性
*/
void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException;
/**
* 刪除Session中的屬性
*/
Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
}
public interface ValidatingSessionManager extends SessionManager {
/**
* 為那些有效的Session進行校驗,如果Session發現是無效的,將會被更改。
* 這個方法期望有規律的執行,如1小時一次,一天一次或一星期一次。執行的頻率取決於應用的效能,使用者活躍數等。
*/
void validateSessions();
}
從上面的介面中可以看出,SessionManager主要負責建立Session和獲取Session,NativeSessionManager介面中包含了所有對Session的操作,這些操作方法和Session介面中是一致的,而ValidatingSessionManager介面提供了對Session校驗的支援。這些介面從功能上分工很明確。
AbstractNativeSessionManager分析
AbstractNativeSessionManager類對NativeSessionManager介面做了一個整體的結構實現,定型了整個介面的實現基礎。我們先列出AbstractNativeSessionManager中主要功能:
- 引用了SessionListen介面來負責對Session狀態的監聽。
- 提供了建立Session的抽象方法createSession(SessionContext context)和獲取Session的抽象方法doGetSession(SessionKey key),這兩個方法都應該從SessionManager介面來實現的。
- 提供了onChange,onStart,onStop,afterStopped鉤子方法。
我們先分析SessionManager中的兩個方法。start(SessionContext context)和getSession(SessionKey key)。
public Session start(SessionContext context) {
// 抽象方法建立Session
Session session = createSession(context);
applyGlobalSessionTimeout(session);
// 鉤子方法,子類實現
onStart(session, context);
// Session監聽器
notifyStart(session);
// 使用DelegatingSession來委託SessionManger處理
return createExposedSession(session, context);
}
protected Session createExposedSession(Session session, SessionContext context) {
return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
}
public Session getSession(SessionKey key) throws SessionException {
Session session = lookupSession(key);
return session != null ? createExposedSession(session, key) : null;
}
private Session lookupSession(SessionKey key) throws SessionException {
if (key == null) {
throw new NullPointerException("SessionKey argument cannot be null.");
}
// 抽象方法
return doGetSession(key);
}
接下來再看看Session監聽器的方法。監聽器提供了對Session狀態的監聽:Session啟動,Session停止,Session過期。
protected void notifyStart(Session session) {
for (SessionListener listener : this.listeners) {
listener.onStart(session);
}
}
protected void notifyStop(Session session) {
Session forNotification = beforeInvalidNotification(session);
for (SessionListener listener : this.listeners) {
listener.onStop(forNotification);
}
}
protected void notifyExpiration(Session session) {
Session forNotification = beforeInvalidNotification(session);
for (SessionListener listener : this.listeners) {
listener.onExpiration(forNotification);
}
}
最後,我們再看看對NativeSessionManager介面方法的實現,方法很多,基本上實現思路一樣。我們以touch和stop來說明。
public void touch(SessionKey key) throws InvalidSessionException {
// 獲取Session
Session s = lookupRequiredSession(key);
// 呼叫Session自己提供的功能
s.touch();
// 呼叫onChange方法作為變更後的後置處理方法
onChange(s);
}
public void stop(SessionKey key) throws InvalidSessionException {
// 獲取Session
Session session = lookupRequiredSession(key);
try {
if (log.isDebugEnabled()) {
log.debug("Stopping session with id [" + session.getId() + "]");
}
// 呼叫Session自己提供的功能
session.stop();
// 呼叫後置處理方法
onStop(session, key);
// 通知監聽器
notifyStop(session);
} finally {
// 呼叫停止後的後置方法
afterStopped(session);
}
}
我們可以給AbstractNativeSessionManager類作一個總結。該類負責管理Session的操作,但操作的具體實現是由Session自己實現的。相當於對Session操作前後做代理(不管是提供鉤子方法還是監聽Session)。
AbstractValidatingSessionManager分析
AbstractValidatingSessionManager的作用是定期的校驗所有有效的Session狀態,因為Session可能被停止或過期。AbstractValidatingSessionManager類繼承了上面分析的AbstractNativeSessionManager,然後實現了ValidatingSessionManager介面中的validateSessions()方法。我們還是先從屬性開始分析,下面是AbstractValidatingSessionManager類中的屬性。
// 標識是否需要校驗Sesion
protected boolean sessionValidationSchedulerEnabled;
// 從名稱上看,我們就知道這個一個排程器。用來定時排程校驗Session
protected SessionValidationScheduler sessionValidationScheduler;
// 排程器排程的時間間隔
protected long sessionValidationInterval;
AbstractValidatingSessionManager定義了一些和排程相關的屬性,我們先了解一下SessionValidationScheduler介面有哪些方法。SessionValidationScheduler提供了3個方法:
- isEnabled() - 表示是否已經開始了排程作業
- enableSessionValidation() - 開啟具體排程的作業內容
- disableSessionValidation() - 停止排程作業
那麼,我們應該想想,排程的作業內容是什麼?很顯然,這個作業內容是校驗Session相關的事情,也就是為什麼在ValidatingSessionManager介面中提供了validateSessions()方法的原因。
明白了SessionValidationScheduler介面之後,我們在回過來分析AbstractValidatingSessionManager就相當容易了。我們先看看是如何校驗的,分析enableSessionValidationIfNecessary()方法。
/**
* 這個方法就是判斷是否需要校驗,如果需要校驗就去開啟排程作業
**/
private void enableSessionValidationIfNecessary() {
SessionValidationScheduler scheduler = getSessionValidationScheduler();
// 首先,判斷是否需要校驗Session,如果為false,則永遠不會開啟校驗,根本就不判斷scheduler的狀態;
// 其次,如果需要校驗Session,會去判斷scheduler== null或scheduler沒有啟動,在這兩種情況下都會去開啟排程任務
// 也就是說,如果任務沒有開啟就去開啟,如果已經開啟了,就不會在處理。
if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
enableSessionValidation();
}
}
/**
* 具體的開啟實現在這裡
**/
protected void enableSessionValidation() {
// 是否設定了scheduler,如果沒有就建立一個
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (scheduler == null) {
// 建立scheduler
scheduler = createSessionValidationScheduler();
setSessionValidationScheduler(scheduler);
}
// 開啟排程作業
scheduler.enableSessionValidation();
// 開啟作業後的後置處理方法(鉤子方法)
afterSessionValidationEnabled();
}
/**
* 建立scheduler
**/
protected SessionValidationScheduler createSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler;
// 注意:這個的this指的就是ValidatingSessionManager介面啦,前面分析過,排程作業的具體內容時由這個介面來提供的。
scheduler = new ExecutorServiceSessionValidationScheduler(this);
scheduler.setInterval(getSessionValidationInterval());
return scheduler;
}
上面我們只是從程式碼片段上分析瞭如何開啟校驗的,現在我們繼續跟隨AbstractNativeSessionManager的分析。我們說在AbstractNativeSessionManager中已經定型了整個類的基本實現,提供了兩個抽象方法createSession(SessionContext context)和doGetSession(SessionKey key)。在AbstractValidatingSessionManager中對這兩個方法進行了實現。下面主要分析這兩個方法是怎麼實現的。
// 實現createSession方法
protected Session createSession(SessionContext context) throws AuthorizationException {
// 這就是上面分析過的方法,判斷是否需要校驗和啟動排程作業
enableSessionValidationIfNecessary();
// 繼續提供抽象方法由子類實現怎麼建立Session
return doCreateSession(context);
}
protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
// 實現doGetSession方法
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
// 這就是上面分析過的方法,判斷是否需要校驗和啟動排程作業
enableSessionValidationIfNecessary();
// 繼續提供抽象方法有子類實現怎麼獲取Session
Session s = retrieveSession(key);
if (s != null) {
// 驗證Session:
// 首先,必須是ValidatingSession介面型別的Session
// 其次,驗證Session是否過期
// 最後,驗證Session是否無效
validate(s, key);
}
return s;
}
protected void validate(Session session, SessionKey key) throws InvalidSessionException {
try {
doValidate(session);
} catch (ExpiredSessionException ese) {
onExpiration(session, ese, key);
throw ese;
} catch (InvalidSessionException ise) {
onInvalidation(session, ise, key);
throw ise;
}
}
protected void doValidate(Session session) throws InvalidSessionException {
if (session instanceof ValidatingSession) {
((ValidatingSession) session).validate();
} else {
String msg = "The " + getClass().getName() + " implementation only supports validating " +
"Session implementations of the " + ValidatingSession.class.getName() + " interface. " +
"Please either implement this interface in your session implementation or override the " +
AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation.";
throw new IllegalStateException(msg);
}
}
從上面的分析可以得出:AbstractValidatingSessionManager類負責校驗Session,對於如何建立和獲取Session,並不是它的需要處理的任務。另外AbstractValidatingSessionManager還實現了Destroyable介面,表示銷燬時應該處理銷燬功能。
protected void disableSessionValidation() {
// 銷燬前置處理方法(鉤子方法)
beforeSessionValidationDisabled();
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (scheduler != null) {
try {
// 停止排程任務
scheduler.disableSessionValidation();
} catch (Exception e) {
if (log.isDebugEnabled()) {
String msg = "Unable to disable SessionValidationScheduler. Ignoring (shutting down)...";
log.debug(msg, e);
}
}
// 生命週期管理
LifecycleUtils.destroy(scheduler);
// 重置scheduler為null
setSessionValidationScheduler(null);
}
}
最後,還有一個重要的功能。上面說到過,排程任務處理的內容是什麼?也就是我們說的ValidatingSessionManager介面提供的validateSessions()方法。
public void validateSessions() {
// 標誌無效的Session個數
int invalidCount = 0;
// 獲取所有有效的Session
// 這是一個抽象方法。因為目前來說,也不知道Session存放在哪裡,要從哪裡獲取呢?
Collection<Session> activeSessions = getActiveSessions();
// 遍歷判斷Session是否有效
if (activeSessions != null && !activeSessions.isEmpty()) {
for (Session s : activeSessions) {
try {
SessionKey key = new DefaultSessionKey(s.getId());
validate(s, key);
} catch (InvalidSessionException e) {
invalidCount++;
}
}
}
if (log.isInfoEnabled()) {
if (invalidCount > 0) {
msg += " [" + invalidCount + "] sessions were stopped.";
} else {
msg += " No sessions were stopped.";
}
log.info(msg);
}
}
同樣,我們也可以給AbstractValidatingSessionManager來總結一下。AbstractValidatingSessionManager類會啟動排程作業來校驗Session,而排程作業的真正內容是檢測每個Session的validate()方法,Session必須是ValidatingSession型別。validate()方法會丟擲兩個異常StoppedSessionException和ExpiredSessionException異常,這個兩個異常都是InvalidSessionException的子類,所以丟擲異常的時候會處理onExpiration()或onInvalidation()方法。
DefaultSessionManager分析
上面分析的都是抽象類,抽象類只是提供了一個基礎的框架,在Shiro中DefaultSessionManager才是我們所使用的SessionManager介面的具體實現類。在分析AbstractValidatingSessionManager的時候,我們說過對於建立和獲取Session,並不是它的職責。Session如何建立的?Session存放在哪裡?我們都還不清楚。在DefaultSessionManager中我們將會知道Shiro是如何做的。還是按照習慣的方式,先看看有哪些屬性和構造方法。
// 從名稱上看就知道這個建立Session的工廠介面
private SessionFactory sessionFactory;
// DAO是做資料儲存的,所以SessionDAO負責Session的CRUD操作
protected SessionDAO sessionDAO;
// 快取管理器,這個很好理解,Session是頻繁使用的物件,需要採用快取功能
private CacheManager cacheManager;
// 是否刪除無效的Session
private boolean deleteInvalidSessions;
// 預設構造方法
public DefaultSessionManager() {
// 刪除無效Session
this.deleteInvalidSessions = true;
// SimpleSessionFactory工廠建立SimpleSession例項
this.sessionFactory = new SimpleSessionFactory();
// 用記憶體儲存Session
this.sessionDAO = new MemorySessionDAO();
}
關於如何建立Session的,放在後面說。上面我們說了丟擲異常的時候會處理onExpiration()或onInvalidation()方法。繼續討論檢測Session無效後是怎麼處理的,下面的方法就是處理Session的實現,如果Session被檢測出被停止或過期就會呼叫相應的方法處理,返回將無效的Session刪除(如果引數配置需要刪除的話)或將Session更新。
@Override
protected void onStop(Session session) {
if (session instanceof SimpleSession) {
SimpleSession ss = (SimpleSession) session;
Date stopTs = ss.getStopTimestamp();
ss.setLastAccessTime(stopTs);
}
onChange(session);
}
@Override
protected void afterStopped(Session session) {
if (isDeleteInvalidSessions()) {
delete(session);
}
}
protected void onExpiration(Session session) {
if (session instanceof SimpleSession) {
((SimpleSession) session).setExpired(true);
}
onChange(session);
}
@Override
protected void afterExpired(Session session) {
if (isDeleteInvalidSessions()) {
delete(session);
}
}
protected void onChange(Session session) {
sessionDAO.update(session);
}
SessionDAO CRUD操作
Session的建立是由SessionFactory來實現的。Shiro只提供了SimpleSessionFactory一個實現類,建立SimpleSession例項。如果需要則可以根據業務擴充套件介面。建立Session後會呼叫SessionDAO#create(session)方法,將Session儲存起來。對Session的CRUD操作都是通過SessionDAO來處理的,SessionDAO負責將Session儲存在哪裡,怎麼儲存。下面簡單地描述一下SessionDAO介面。
public interface SessionDAO {
/**
* 插入Session(可以是資料庫,檔案系統,記憶體,快取等)
*/
Serializable create(Session session);
/**
* 獲取Session
*/
Session readSession(Serializable sessionId) throws UnknownSessionException;
/**
* 更新Session
*/
void update(Session session) throws UnknownSessionException;
/**
* 刪除Session
*/
void delete(Session session);
/**
* 返回所有活動的Session
*/
Collection<Session> getActiveSessions();
}
Shiro提供了兩種SessionDAO,第一種是MemorySessionDAO,它將Session儲存在記憶體中;第二種是EnterpriseCacheSessionDAO,可以定義將Session存入到快取中。由於在使用中我們很大可能需要自定義SessionDAO,下面對SessionDAO也展開分析。MemorySessionDAO是以Map作為儲存的,很簡單不再說明。我們以EnterpriseCacheSessionDAO來分析。EnterpriseCacheSessionDAO類的繼承關係是這樣的:EnterpriseCacheSessionDAO->CachingSessionDAO->AbstractSessionDAO。在AbstractSessionDAO中提供了SessionIdGenerator型別的屬性,用於生成Session唯一ID值。我們需要分析的重點是CachingSessionDAO。
CachingSessionDAO分析
CachingSessionDAO是一個抽象類,負責對Session進行快取管理,所有Session都存入到一個快取中。Shiro提供自己的Cache和CacheManager兩個介面。CacheManger負責管理Cache物件例項。下面是CachingSessionDAO的屬性,我們可以看出Session就是存放在activeSessions中。
/**
* 預設Session快取名稱
*/
public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
/**
* 快取管理器
*/
private CacheManager cacheManager;
/**
* 儲存Session的快取物件
*/
private Cache<Serializable, Session> activeSessions;
/**
* Session快取名
*/
private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;
看看在CachingSessionDAO是怎樣來建立Session,獲取Session,更新Session的。
// 介面方法,建立Session
public Serializable create(Session session) {
Serializable sessionId = super.create(session);
// 相當於對父類方法做了後置快取處理
// 實際上就是呼叫Cache#put()方法,將Session儲存
cache(session, sessionId);
return sessionId;
}
// 實現父類抽象方法,獲取Session
public Session readSession(Serializable sessionId) throws UnknownSessionException {
// 相當於對父類方法做了前置處理
// 先從快取中獲取,如果快取中沒有再呼叫真實方法
Session s = getCachedSession(sessionId);
if (s == null) {
s = super.readSession(sessionId);
}
return s;
}
// 修改Session
public void update(Session session) throws UnknownSessionException {
// 抽象方法,修改Session前置處理
doUpdate(session);
// 將Session更新到快取中
if (session instanceof ValidatingSession) {
if (((ValidatingSession) session).isValid()) {
cache(session, session.getId());
} else {
uncache(session);
}
} else {
cache(session, session.getId());
}
}
// 刪除Session
public void delete(Session session) {
// 從快取中刪除
uncache(session);
// 刪除後置方法
doDelete(session);
}
總結
在本篇中我們瞭解了Session、SessionManager以及SessionDAO。Session表示使用者的會話資料,Session的核心點是狀態,Session有無效和活動兩種狀態。其中,無效又包括被停止和過期狀態,我們可以對Session狀態進行監聽和檢測。
另外,就是Session的儲存,在Shiro中使用SessionDAO介面來處理Session的儲存,Shiro中提供了基於記憶體和基於快取的兩種方式來儲存Session。