Shiro源代碼分析之兩種Session的方式
1、Shiro默認的Session處理方式
<!-- 定義 Shiro 主要業務對象 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- <property name="sessionManager" ref="sessionManager" /> --> <property name="realm" ref="systemAuthorizingRealm" /> |
這裏從DefaultWebSecurityManager這裏看起。這個代碼是定義的Shiro安全管理對象,看以下的構造方法(代碼 1-1)
(代碼 1-1) public DefaultWebSecurityManager() { super(); ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator()); |
從 構造方法裏面能夠看出,這裏面有 publicvoid setRememberMeManager(RememberMeManager rememberMeManager)
這個類通過getSession(SessionKey)獲得Sesison,以下看看這種方法幹了些什麽(代碼1-2)
(代碼1-2) publicSession getSession(SessionKey key) throws SessionException { if (!WebUtils.isHttp(key)) { //推斷是不是http的key,否則拋異常 String msg = "SessionKey must be an HTTP compatible implementation."; throw new IllegalArgumentException(msg); } HttpServletRequest request = WebUtils.getHttpRequest(key); //通過工具類獲得HttpServletRequest 這類是javax.servlet.http.HttpServletRequest; Session session = null; HttpSession httpSession = request.getSession(false);//先從request裏獲得本來存在的 if (httpSession != null) { session = createSession(httpSession, request.getRemoteHost());//假設不為空,就創建一個封裝了的,為空就無論它 } return session; } |
能夠看看凝視,凝視上都寫好了。這裏的意思是。首先推斷封裝好的Key是不是Http的key。假設是就繼續,不是就拋異常.key這個是帶了ServletRequest和ServletResponse的WebSessinKey,詳細怎麽new出來的,看DefaultWebSecurityManager的這方法代碼就知道了(代碼1-3)。這裏裏,將Request和Response還有sessionId傳遞進去
(代碼1-3) @Override protected SessionKey getSessionKey(SubjectContext context) { if (WebUtils.isWeb(context)) { Serializable sessionId = context.getSessionId(); ServletRequest request = WebUtils.getRequest(context); ServletResponse response = WebUtils.getResponse(context); return new WebSessionKey(sessionId, request, response); } else { return super.getSessionKey(context); } } |
由於繼承了 org.apache.shiro.session.mgt.DefaultSessionKey ,這個類是實現了SessionKey這個接口。 看看createSession這種方法,這種方法就是將傳入的HttpSession和host傳進去,封裝成Shiro的HttpServletSession (代碼 1-4)
(代碼1-4) protectedSession createSession(HttpSession httpSession, String host) { return new HttpServletSession(httpSession, host); } |
關於默認的Session管理器,最後還看一下HttpServletSession這個類。就看一下的幾個方法。還有些方法是沒有放出來的,能夠明顯的看出。Shiro使用了一個叫Session的接口。但這個接口是和HttpSession的接口一模一樣。就是通過HttpSession這個接口獲得Session的功能(代碼1-5)
(代碼1-5) public class HttpServletSession implements Session { private HttpSession httpSession = null; public HttpServletSession(HttpSession httpSession, String host) { if (httpSession == null) { String msg = "HttpSession constructor argument cannot be null."; throw new IllegalArgumentException(msg); } if (httpSession instanceof ShiroHttpSession) { String msg = "HttpSession constructor argument cannot be an instance of ShiroHttpSession. This " + "is enforced to prevent circular dependencies and infinite loops."; throw new IllegalArgumentException(msg); } this.httpSession = httpSession; if (StringUtils.hasText(host)) { setHost(host); } } …………………… …………………… ……………… public Object getAttribute(Object key) throws InvalidSessionException { try { return httpSession.getAttribute(assertString(key)); } catch (Exception e) { throw new InvalidSessionException(e); } } public void setAttribute(Object key, Object value) throws InvalidSessionException { try { httpSession.setAttribute(assertString(key), value); } catch (Exception e) { throw new InvalidSessionException(e); } } ……………… ……………… } |
默認的模式就描寫敘述到這裏
2、接下來說另外一種方式。就是廢棄掉tomcat自己的Session,使用企業級Session方案。這樣的方案能夠和容器無關。但在我們項目沒有使用,由於用了開源連接池druid貌似logout的時候有點不正確。
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="3600000" /> <property name="sessionDAO" ref="sessionDAO" /> </bean> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <property name="activeSessionsCacheName" value="shiro-activeSessionCache" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean> <!-- 用戶授權信息Cache, 採用EhCache --> <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </bean>
必須向Spring註冊EnterpriseCacheSessionDAO然後將cacheManager註入進去
這樣的配置的創建入口在SecurityUtils.getSubject().getSession();這裏看以下代碼(代碼2-1)
(代碼2-1) publicSession getSession(boolean create) { if (log.isTraceEnabled()) { log.trace("attempting to get session; create = " + create + "; session is null = " + (this.session == null) + "; session has id = " + (this.session != null && session.getId() != null)); } if (this.session == null && create) {//session為空,而且能創建 …… ……省略 …… SessionContext sessionContext = createSessionContext(); Session session = this.securityManager.start(sessionContext);//在這裏創建Session this.session = decorate(session);//包裝Session,他自己建的自己也去包裝一下 } |
調用DefaultSecurityManager的父類SessionsSecurityManager的Sessionstart(SessionContext context)。以下是這種方法的代碼(代碼2-2)
(代碼2-2) public Session start(SessionContext context) throws AuthorizationException { return this.sessionManager.start(context); } |
然後調用sessionManager的start方法來創建Session。創建Session的入口。就在這裏。看以下代碼分析(代碼2-3)
(代碼2-3) publicSession start(SessionContext context) { Session session = createSession(context);//創建Session applyGlobalSessionTimeout(session); onStart(session, context); notifyStart(session); return createExposedSession(session, context); } |
創建Session這裏是調用?DefaultSessionManager的父類的createSession,事實上父類也沒有真正來創建Session。
這裏用到了模板方法。父類裏面的doCreateSession是抽象方法,最後真正創建子類的還是交給子類去實現(代碼2-4)
(代碼2-4) protectedSession createSession(SessionContext context) throwsAuthorizationException { enableSessionValidationIfNecessary(); return doCreateSession(context); } protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException; |
?其它的也沒多少可分析的。這裏再看一下manager裏面的sessionFacotry工廠的createSession方法(代碼2-5)
(代碼2-5) publicSession createSession(SessionContext initData) { if (initData != null) { String host = initData.getHost(); if (host != null) { return new SimpleSession(host); } } return new SimpleSession(); } |
這裏的SimpleSession是實現了Session接口的。詳細能夠看看相關的類繼承圖
另外Session是怎麽緩存進入Cache的呢?在以下的調用以下代碼創建Session的過程中,以下方法會調用,而緩存就在create(s)這裏面(代碼2-6)
(代碼2-6) protected Session doCreateSession(SessionContext context) { Session s = newSessionInstance(context); if (log.isTraceEnabled()) { log.trace("Creating session for host {}", s.getHost()); } create(s); return s; } |
經過一些步驟之後在CachingSessionDao裏被緩存,以下是代碼。
能夠看以下的凝視(代碼2-7)
(代碼2-7) protectedvoid cache(Session session, Serializable sessionId) { if (session == null || sessionId == null) { return; } Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();//獲取緩存 if (cache == null) { return; } cache(session, sessionId, cache);//有緩存就存起來 } |
以上是Session的創建過程,獲取Session就簡單說吧。有些過程自己發現更有趣。這裏會調用DefaultWebSessionManager的父類的getAttribute這種方法(代碼2-8)
(代碼2-8) publicObject getAttribute(SessionKey sessionKey, Object attributeKey)throws InvalidSessionException { return lookupRequiredSession(sessionKey).getAttribute(attributeKey); } |
?最後會調用CachingSessionDao的這個publicSession readSession(Serializable sessionId) throwsUnknownSessionException 在這裏就會從緩存裏讀取Session(代碼2-9)
(代碼2-9) public Session readSession(Serializable sessionId) throws UnknownSessionException { Session s = getCachedSession(sessionId); if (s == null) { s = super.readSession(sessionId); } return s; } |
?這是getCachedSession(sessionId)的代碼。看了代碼想必非常easy理解了吧(代碼2-10)
protected Session getCachedSession(Serializable sessionId) { Session cached = null; if (sessionId != null) { Cache<Serializable, Session> cache = getActiveSessionsCacheLazy(); if (cache != null) { cached = getCachedSession(sessionId, cache); } } return cached; } |
?獲得了Session,要獲得裏面的值和對象就非常easy了
有問題歡迎提出來。由於是先寫在編輯器上的。然後在復制到word上。所以代碼是一致的黑色,希望可以講究著看,寫個原創文章不easy,眼睛都看腫了。所以轉載的時候能帶上作者,謝謝
作者:肖華
blog.csdn.net/xh199110 飛丶天
Shiro源代碼分析之兩種Session的方式