Shiro源碼分析之兩種Session的方式

原文鏈接:http://www.th7.cn/Program/java/201507/513741.shtml




1Shiro默認的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)兩個方法。這兩個分別是對ShiroremembereMe功能和Session功能的管理。其中在構造方法裏面可以看到,其實一開是就設置了SessionManager,就是默認的:ServletContainerSessionManager().接下來看看默認的ServletContainerSessionManager()是怎麼玩轉Session.看下圖。這個圖顯示了這個類的這些個方法


這個類通過getSession(SessionKey)獲得Sesison,下面看看這個方法幹了些什麼(代碼1-2

(代碼1-2

publicSession getSession(SessionKey key) throws SessionException {

        if (!WebUtils.isHttp(key)) { //判斷是不是httpkey,否則拋異常
            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是不是Httpkey,如果是就繼續,不是就拋異常.key這個是帶了ServletRequestServletResponseWebSessinKey,具體怎麼new出來的,看DefaultWebSecurityManager的這方法代碼就知道了(代碼1-3,這裏裏,將RequestResponse還有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這個方法,這個方法就是將傳入的HttpSessionhost傳進去,封裝成ShiroHttpServletSession (代碼 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的父類SessionsSecurityManagerSessionstart(SessionContext context),下面是這個方法的代碼(代碼2-2

(代碼2-2

public Session start(SessionContext context) throws AuthorizationException {
        return this.sessionManager.start(context);
    }


然後調用sessionManagerstart方法來創建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 飛丶天


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章