開心一刻 html
老公酷愛網絡遊戲,老婆無奈,只得告誡他:你玩就玩了,可是千萬不能夠在遊戲裏找老婆,否則,哼哼。。。
老公嘴角露出了微笑:放心吧親愛的,我絕對不會在遊戲裏找老婆的!由於我有老公!
老婆:......git
路漫漫其修遠兮,吾將上下而求索!github
github:https://github.com/youzhibing網絡
碼雲(gitee):https://gitee.com/youzhibingsession
你們還記得上篇博文講了什麼嗎,咱們來一塊兒簡單回顧下:ide
SecurityManager是shiro的核心,負責與shiro的其餘組件進行交互;SessionManager是session的真正管理者,負責shiro的session管理;this
SessionsSecurityManager的start方法中將session的建立委託給了具體的sessionManager,是建立session的關鍵入口。spa
SimpleSession是shiro完徹底全的本身實現,是shiro對session的一種拓展;實現了ValidatingSession接口,具備自我校驗的功能;通常不對外暴露,暴露的每每是他的代理:DelegatingSession;SimpleSession有幾個屬性值得重點關注下,以下debug
id:就是session id;代理
startTimestamp:session的建立時間;
stopTimestamp:session的失效時間;
lastAccessTime:session的最近一次訪問時間,初始值是startTimestamp
timeout:session的有效時長,默認30分鐘
expired:session是否到期
attributes:session的屬性容器
session的建立完成後,會將session(SimpleSession類型)對象的代理對象(DelegatingSession)裝飾成StoppingAwareProxiedSession對象,而後綁定到subject(類型是DelegatingSubject);
Session session = subject.getSession();返回的就是綁定在當前subjuct的session。注意subject的實際類型是:DelegatingSubject,以下圖
shiro的Session接口提供了一個touch方法,負責session的刷新;session的代理對象最終會調用SimpleSession的touch():
public void touch() { this.lastAccessTime = new Date(); // 更新最後被訪問時間爲當前時間 }
可是touch方法是何時被調用的呢?JavaSE須要咱們本身按期的調用session的touch() 去更新最後訪問時間;若是是Web應用,每次進入ShiroFilter都會自動調用session.touch()來更新最後訪問時間,ShiroFilter的類圖以下:
ShiroFilter自動調用session.touch()以下
若是是讓咱們本身實現session過時的判斷,咱們會怎麼作了?咱們來看看shiro是怎麼作的,或許咱們可以從中學到一些經驗。
還記得AbstractValidatingSessionManager中createSession方法嗎?在調用doCreateSession方法以前調用enableSessionValidationIfNecessary(),enableSessionValidationIfNecessary代碼以下
private void enableSessionValidationIfNecessary() { // 獲取session驗證調度器 SessionValidationScheduler scheduler = getSessionValidationScheduler(); // session驗證調度器開啓 && (調度器爲空或調度器不可用) if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) { enableSessionValidation(); // 開啓session驗證 } }
第一次建立session的時候,若是session驗證調度器啓用(默認是啓用),那麼調用enableSessionValidation(),enableSessionValidation代碼以下
protected synchronized void enableSessionValidation() { SessionValidationScheduler scheduler = getSessionValidationScheduler(); // 獲取調取器 if (scheduler == null) { scheduler = createSessionValidationScheduler(); // 建立調取器,實際類型是ExecutorServiceSessionValidationScheduler setSessionValidationScheduler(scheduler); // 將調度器綁定到sessionManager } // it is possible that that a scheduler was already created and set via 'setSessionValidationScheduler()' // but would not have been enabled/started yet if (!scheduler.isEnabled()) { if (log.isInfoEnabled()) { log.info("Enabling session validation scheduler..."); } scheduler.enableSessionValidation(); // 啓動定時任務,驗證session afterSessionValidationEnabled(); // 什麼也沒作,供繼承,便於拓展 } }
ExecutorServiceSessionValidationScheduler類圖以下,它實現了Runnable接口
調用scheduler的enableSessionValidation(),enableSessionValidation方法以下
public void enableSessionValidation() { if (this.interval > 0l) { // 建立ScheduledExecutorService this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { private final AtomicInteger count = new AtomicInteger(1); public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); thread.setName(threadNamePrefix + count.getAndIncrement()); return thread; } }); // 初始化service interval時長以後開始執行this的run方法,每隔interval執行一次;注意interval的單位是TimeUnit.MILLISECONDS this.service.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS); // this就是ExecutorServiceSessionValidationScheduler本身 } this.enabled = true; }
定時(默認每隔60分鐘)的調用ExecutorServiceSessionValidationScheduler的run方法,run方法中調用sessionManager的validateSessions方法來完成session的驗證,validateSessions方法以下
/** * @see ValidatingSessionManager#validateSessions() */ public void validateSessions() { if (log.isInfoEnabled()) { log.info("Validating all active sessions..."); } int invalidCount = 0; // 從sessionDao中獲取所有的session // sessionDao能夠是默認的MemorySessionDAO,也能夠是咱們定製的CachingSessionDAO Collection<Session> activeSessions = getActiveSessions(); if (activeSessions != null && !activeSessions.isEmpty()) { // 一個一個校驗 for (Session s : activeSessions) { try { //simulate a lookup key to satisfy the method signature. //this could probably stand to be cleaned up in future versions: SessionKey key = new DefaultSessionKey(s.getId()); validate(s, key); // 真正校驗的方法 } catch (InvalidSessionException e) { if (log.isDebugEnabled()) { boolean expired = (e instanceof ExpiredSessionException); String msg = "Invalidated session with id [" + s.getId() + "]" + (expired ? " (expired)" : " (stopped)"); log.debug(msg); } invalidCount++; // 統計上次到此次定時任務間隔內過時的session個數 } } } if (log.isInfoEnabled()) { String msg = "Finished session validation."; if (invalidCount > 0) { msg += " [" + invalidCount + "] sessions were stopped."; } else { msg += " No sessions were stopped."; } log.info(msg); } }
validate方法以下
protected void validate(Session session, SessionKey key) throws InvalidSessionException { try { doValidate(session); // 真正校驗session } catch (ExpiredSessionException ese) { onExpiration(session, ese, key); // 從sessionDao中刪除過時的session throw ese; // 拋出異常供上層統計用 } catch (InvalidSessionException ise) { onInvalidation(session, ise, key); // 從sessionDao中刪除不合法的session throw ise; // 拋出異常供上層統計用 } }
經過捕獲doValidate()拋出的異常來剔除過時的或不合法的session,並將異常接着往上拋,供上層統計過時數量。注意:ExpiredSessionException的父類是StoppedSessionException,而StoppedSessionException的父類是InvalidSessionException。
doValidate方法以下
protected void doValidate(Session session) throws InvalidSessionException { if (session instanceof ValidatingSession) { ((ValidatingSession) session).validate(); // 校驗session是否過時 } else { // 若session不是ValidatingSession類型,則拋出IllegalStateException異常 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); } }
若session不是ValidatingSession類型,則拋出IllegalStateException異常
validate方法以下
public void validate() throws InvalidSessionException { //check for stopped: if (isStopped()) { // sesson已經中止了,則拋出StoppedSessionException;理論上來說不會出現這種狀況,但程序的事沒有100%保障 //timestamp is set, so the session is considered stopped: String msg = "Session with id [" + getId() + "] has been " + "explicitly stopped. No further interaction under this session is " + "allowed."; throw new StoppedSessionException(msg); } //check for expiration if (isTimedOut()) { // 校驗是否過時,校驗方法是:lastAccessTime是否小於(當前時間 - session有效時長) expire(); // 更新session的stopTimestamp爲當前時間,session的expired爲true //throw an exception explaining details of why it expired: Date lastAccessTime = getLastAccessTime(); long timeout = getTimeout(); Serializable sessionId = getId(); DateFormat df = DateFormat.getInstance(); String msg = "Session with id [" + sessionId + "] has expired. " + "Last access time: " + df.format(lastAccessTime) + ". Current time: " + df.format(new Date()) + ". Session timeout is set to " + timeout / MILLIS_PER_SECOND + " seconds (" + timeout / MILLIS_PER_MINUTE + " minutes)"; if (log.isTraceEnabled()) { log.trace(msg); } throw new ExpiredSessionException(msg); // 拋出ExpiredSessionException供上層使用 } }
一、sesion的有效時長默認30分鐘;定時任務默認是每60分鐘執行一次,第一次執行是在定時器初始化完成60分鐘後執行;
二、session不是ValidatingSession類型,則拋出IllegalStateException異常;session已經中止了則拋出StoppedSessionException;session過時則拋出ExpiredSessionException異常;理論上來說IllegalStateException與StoppedSessionException不會被拋出,應該全是ExpiredSessionException異常;ExpiredSessionException繼承自StoppedSessionException,而StoppedSessionException又繼承自IllegalStateException;
三、校驗session的時候,拋出了異常,將其捕獲,從sessionDao中刪除對應的session,並使過時數量自增1
夾雜在過時定時任務中,與過時是同時進行的,利用的異常機制;固然session操做的時候sessionManager也有session的校驗,伴隨着就有session的刪除。
定時任務默認每60分鐘執行一次,而session有效時長默認是30分鐘,那麼定時任務執行的間隔內確定有session過時了,而咱們在這個間隔內操做了過時的session怎麼辦?
其實這個問題應該這麼來問:在定時任務間隔期間,對session的操做有沒有作校驗處理?答案是確定的。
經過上面的講解咱們知道:session的操做經過代理以後,都會來到sessionManager,sessionManager經過處理以後再到SimpleSession;AbstractNativeSessionManager中將session操做放給SimpleSession以前,都會調用lookupSession方法,跟進lookupSession你會發現,裏面也有session的校驗。
因此session的校驗,不僅是定製任務在執行,不少session的操做都有作session的校驗。
一、通常咱們操做subject是DelegatingSubject類型,DelegatingSubject中將subject的操做委託給了securityManager;通常操做的session是session的代理,代理將session操做委託給sessionManager,sesionManager校驗以後再轉交給SimpleSession;
二、session過時定時任務默認60分鐘執行一次,所session已過時或不合法,則拋出對應的異常,上層經過捕獲異常從sessionDao中刪除session
三、不僅定時任務作session的校驗,session的基本操做都在sessionManager中有作session的校驗
《跟我學shiro》