1. 程式人生 > >Shiro原始碼分析(2) - 會話管理器(SessionManager)

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。