原文鏈接:http://www.th7.cn/Program/java/201507/513741.shtml
1、Shiro默認的Session處理方式
<!-- 定義 Shiro 主要業務對象 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- <property name="sessionManager" ref="sessionManager" /> --> <property name="realm" ref="systemAuthorizingRealm" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean> |
這裏從DefaultWebSecurityManager這裏看起,這個代碼是定義的Shiro安全管理對象,看下面的構造方法(代碼 1-1)
(代碼 1-1) public DefaultWebSecurityManager() { super(); ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator()); this.sessionMode = HTTP_SESSION_MODE; setSubjectFactory(new DefaultWebSubjectFactory()); setRememberMeManager(new CookieRememberMeManager()); setSessionManager(new ServletContainerSessionManager()); } |
從 構造方法裏面可以看出,這裏面有 publicvoid setRememberMeManager(RememberMeManager rememberMeManager) 和 publicvoid setSessionManager(SessionManager sessionManager)兩個方法。這兩個分別是對Shiro的remembereMe功能和Session功能的管理。其中在構造方法裏面可以看到,其實一開是就設置了SessionManager,就是默認的:ServletContainerSessionManager().接下來看看默認的ServletContainerSessionManager()是怎麼玩轉Session的.看下圖。這個圖顯示了這個類的這些個方法
這個類通過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)的代碼。看了代碼想必很容易理解了吧(代碼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,要獲得裏面的值和對象就很容易了
有問題歡迎提出來,因爲是先寫在編輯器上的,然後在拷貝到word上,所以代碼是一致的黑色,希望能夠講究着看,寫個原創文章不容易,眼睛都看腫了,所以轉載的時候能帶上作者,謝謝
作者:肖華
blog.csdn.net/xh199110 飛丶天