從shiro原始碼角度學習建造者設計模式

深夜程猿發表於2018-01-15

緒論

今天就和大家分享一下shiro原始碼裡面使用到的建造者模式。在介紹建造者模式相關知識之前,我們先來看一段例子分析。

從一個簡單的例子說起

我們在使用shiro獲取登入使用者的時候,比如使用ini檔案配置使用者角色許可權,我們可以這樣寫:

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:demo01_getstarted.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
複製程式碼

SecurityUtils.getSubject()是獲取當前使用者,我們跳進去看一下,是怎麼獲取的

 public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }
複製程式碼

上面程式碼,首先從ThreadContext裡面獲取Subject,獲取不到就建立,我們看看建立過程,跳進去看看(new Subject.Builder()).buildSubject()幹了什麼。 這裡不貼過長程式碼了,Builder是Subject的靜態內部類,用來完成Subject的複雜建立過程,使得呼叫就無需關注Subject怎麼建立的,只要知道怎麼獲取Subject就可以了。 我們看一下,Builder類裡面幹了什麼事情

 public static class Builder {

        private final SubjectContext subjectContext;

        private final SecurityManager securityManager;

        public Builder() {
            this(SecurityUtils.getSecurityManager());
        }

        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
            this.securityManager = securityManager;
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                        "cannot be null.");
            }
            this.subjectContext.setSecurityManager(securityManager);
        }

        protected SubjectContext newSubjectContextInstance() {
            return new DefaultSubjectContext();
        }

        protected SubjectContext getSubjectContext() {
            return this.subjectContext;
        }

        public Builder sessionId(Serializable sessionId) {
            if (sessionId != null) {
                this.subjectContext.setSessionId(sessionId);
            }
            return this;
        }

        public Builder host(String host) {
            if (StringUtils.hasText(host)) {
                this.subjectContext.setHost(host);
            }
            return this;
        }

        public Builder session(Session session) {
            if (session != null) {
                this.subjectContext.setSession(session);
            }
            return this;
        }

        public Builder principals(PrincipalCollection principals) {
            if (principals != null && !principals.isEmpty()) {
                this.subjectContext.setPrincipals(principals);
            }
            return this;
        }

        public Builder sessionCreationEnabled(boolean enabled) {
            this.subjectContext.setSessionCreationEnabled(enabled);
            return this;
        }

        public Builder authenticated(boolean authenticated) {
            this.subjectContext.setAuthenticated(authenticated);
            return this;
        }

        public Builder contextAttribute(String attributeKey, Object attributeValue) {
            if (attributeKey == null) {
                String msg = "Subject context map key cannot be null.";
                throw new IllegalArgumentException(msg);
            }
            if (attributeValue == null) {
                this.subjectContext.remove(attributeKey);
            } else {
                this.subjectContext.put(attributeKey, attributeValue);
            }
            return this;
        }
        // 建立Subject
        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }
    }
複製程式碼

我們可以看到,Builder完成了Subject一些列的複雜建立過程,包括sesson、sessionid等的設定等過程。最後會呼叫buildSubject()方法來建立Subject。 我們就不繼續深入看this.securityManager.createSubject(this.subjectContext)裡面幹了什麼事了。 回到前面,我們可以看到Subject物件會包含許多的當前使用者資訊,但是這些資訊框架底層都替我們設定好了,我們只要獲取Subject物件即可,呼叫Subject currentUser = SecurityUtils.getSubject();就可以了。 這樣看來,是不是特別方便。這些都歸功於建造這模式的使用。

建造這模式基本概念

通過分析shiro原始碼,我們對建造這模式有了一個初步的感知,目前大家可以理解成建立複雜物件由一個建造類來完成。那麼,我們就來學習一下建造者模式的一些理論知識,相信大家結合上面的例子分析,不會感到吃力乏味的。

什麼是建造者模式

建造者模式又稱為生成器模式。
建造者模式:它可以將複雜物件的建造過程抽象出來(抽象類別),使這個抽象過程的不同實現方法可以構造出不同表現(屬性)的物件。 參考維基百度百科:生成器模式
可能有些讀者看了這句話迷迷糊糊的,那麼這句話是什麼意思呢?結合上面的例子,我們可以知道Subject物件有很多屬性,通過SubjectContext物件來儲存的,最後建立Subject物件的時候,傳遞SubjectContext就可以了。這樣比如,我們建立兩個有不同屬性值的Subject:

        Subject user_1 = new Subject.Builder().authenticated(false).buildSubject();
        Subject user_2 = new Subject.Builder().authenticated(true).buildSubject();
複製程式碼

我們建立Subject的不同實現方法,比如authenticated()一個設定true,一個設定false,我們就構造出來了有不同表現(屬性)的Subject物件了,一個是授權,一個是未授權。

為什麼使用建造者模式

使用建造者模式可以遮蔽複雜的物件建立過程,物件的建立對呼叫者來說是透明的,這樣就使得程式耦合度降低,程式可讀性和維護性提高了。

結束語

對於設計模式,大家不要機械地學習,個人覺得主要理解其設計思想和好處就可以了。在編碼過程中大家不要刻意去使用它,而是去思考如何運用它更好地設計程式碼。這樣,經過時間的推磨,大家就可以真正做到把設計模式融會貫通,手到拈來。

相關文章