淺談 J2EE Spring 框架中涉及到的設計原則和設計模式

因爲筆者本學期自學了 SSM 三大框架以及 SpringBoot 框架,並且也做了兩個基於 SSM 的小項目。於是對其中的最核心的 Spring 框架也有了一定的瞭解,於是想借此機會來剖析剖析其涉及到的設計思想,希望在這個過程中能進一步理解 Spring 的底層思想以及相關的設計思想的原理和應用。

1. 一句話概括 Spring 框架

Spring 是一個輕量級的控制反轉(IoC)和麪向切面(AOP)的容器框架。

2. 從 Spring 的兩大核心開始

​ 要談到 Spring,那必須談及其最核心的兩個部分,就是控制反轉 IoC 和 面向切面編程 AOP。

2.1 IoC

IoC 的中文意思就是“控制反轉”。那什麼叫“控制反轉”呢?筆者是這麼理解的:比如我們要想要喫一個麪包,第一種途徑就是自己動手,豐衣足食。我們可以自己去買麪粉以及其他各種材料,自己在家裏製作一個麪包,這樣我們可以任意地 DIY 我們的麪包,包括口味,包括外形。當然,一般人都會選擇第二種途徑,那就是去麪包店購買麪包,我們甚至可以提供我們對口味、對外形的要求,定製我們想要的麪包。這樣生產麪包的職責,就從我們手裏反轉到麪包店手裏了,即把製作麪包的主動權交給了店家。

用專業的話來講呢,在平時的java應用開發中,我們要實現某一個功能或者說是完成某個業務邏輯時至少需要兩個或以上的對象來協作完成,在沒有使用 Spring 的時候,每個對象在需要使用他的合作對象時,自己均要使用像 new object() 這樣的語法來將合作對象創建出來,這個合作對象是由自己主動創建出來的,創建合作對象的主動權在自己手上,自己需要哪個合作對象,就主動去創建,創建合作對象的主動權和創建時機是由自己把控的。

  • 這樣會有什麼缺點呢?—— 耦合度太高。

    A對象需要使用合作對象B來共同完成一件事,A要使用B,那麼A就對B產生了依賴,也就是A和B之間存在一種耦合關係,並且是緊密耦合在一起。

使用 Spring 框架後就不一樣了,創建合作對象B的工作是由 Spring 來做的,Spring 創建好B對象,然後存儲到一個容器裏面,當A對象需要使用B對象時,Spring 就從存放對象的那個容器裏面取出A要使用的那個B對象,然後交給A對象使用,至於 Spring 是如何創建那個對象,以及什麼時候創建好對象的,A對象不需要關心這些細節問題,A得到 Spring 給我們的對象之後,兩個人一起協作完成要完成的工作即可。

2.2 AOP

Spring 框架中的 AOP 是一種面向切面編程的思想。用通俗易懂的話來講就是抽取出分散在各個方法中的重複代碼,然後在程序編譯或運行階段將這些抽取處理的代碼插入到需要執行的地方。如下圖所示,這樣我們就只需要專注於實現軟件的業務邏輯模塊即可。

image-20200612022618370

3. IoC 涉及到的面向對象設計原則

3.1 依賴倒轉原則

高層模塊不應該依賴低層模塊,它們都應該依賴抽象。抽象不應該依賴於細節,細節應該依賴於抽象。

針對抽象層編程,將具體類的對象通過依賴注入(Dependency Injection, DI)的方式注入到其他對象。

在 Spring 框架中實現 IoC 容器的方法就是依賴注入,其作用是在使用 Spring 框架創建對象時動態地將其所依賴的底下注入 Bean 組件中,其目標是讓調用者不要主動去使用被調用者,而是讓被調用者向調用者提供服務。IoC 和 bean對象 的配合完美實現了這個過程,我們可以使用 @Component 、@Service、@Controller、@Repository 等註解添加一個 bean 到 IoC 容器,然後通過 @autowired 註解就可以讓 IoC 容器自動找到對應的類注入進來。

3.2 接口隔離原則

客戶端不應該依賴那些它不需要的接口。

Spring 的 IoC 容器不僅僅可以通過一個類的類型來實現依賴注入,也可以通過接口類型來實現依賴注入,我們只需要讓接口更加專門化,然後將其放入 IoC 容器中,需要的時候使用 Spring 進行注入即可。

3.3 迪米特法則

每一個軟件單位對其他的單位都只有最少的知識,而且侷限於那些與本單位密切相關的軟件單位

迪米特法則要求一個軟件實體應當儘可能少地與其他實體發生相互作用。

從Spring IoC 的機制上來說,天然就降低了不同實體之間的依賴關係,因爲我們不是通過 new 的方式來創建對象,而且通過依賴注入的方式,所以很充分地體現了迪米特法則。

3.4 開閉原則

軟件實體應當對擴展開放,對修改關閉。

至於開閉原則那就更加明顯了,我們基於 Spring 框架做開發,在構建類時大多數都是按照該原則 —— 新功能構建新的類來實現,而並非在原有的類的基礎上添加新的方法。

4. IoC 涉及到的設計模式

4.1 工廠模式

Spring 中 Bean 對象的創建就是典型的工廠模式。我們可以看一下下面這個圖:

image-20200612032217375

這一系列的 Bean 工廠, 爲開發者管理對象間的依賴關係提供了很多便利和基礎服務, 在 Spring 中有許多的 IOC 容器的實現供用戶選擇和使用。

舉一個最簡單的例子,我們來看看 Spring 是如何利用工廠模式來幫助我們創建 Bean 對象的。先看下面代碼:

public static void main(String[] args) {
        //1. 獲取核心容器對象
        ApplicationContext appCon = new ClassPathXmlApplicationContext("bean.xml");
        //2. 根據 id 獲取 bean 對象
        IAccountService as = (IAccountService)appCon.getBean("accountService");
  			//3. 使用創建出來的 bean 對象
        as.accountSave();
}

這個 ApplicationContext 是什麼呢?從名字上看它跟“工廠”好像沒什麼關係。其實不然,我們可以來看看與它相關聯的一些類:

image-20200612033427825

從上圖我們可以看到 ApplicationContext 這個接口其實繼承了很多個工廠接口。實際上,ApplicationContext 作爲 Spring 對外的門面,在Spring 提供了不同的實現,用來通過不同的途徑,加載配置,實例化 Spring 容器。通過這種方式,實現接口編程,屏蔽實現細節,增強Spring 的可擴展性。

4.2 工廠方法模式

一個典型例子:Spring 與 Mybatis 的結合

    <!-- 4、配置和 Mybatis 的整合-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 4-1 指定 Mybatis 全局配置文件的位置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml" ></property>
        <!-- 4-2 指定數據源 -->
        <property name="dataSource" ref="pooledDataSource"></property>
        <!-- 4-3 指定 Mapper 配置文件 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml" ></property>
    </bean>

這裏我們往 Spring 容器裏面配置一個 id 爲 sqlSessionFactory 的 bean 對象,我們指定類類型是 SqlSessionFactoryBean,但實際上在創建這個 bean 對象的時候,我們生成的對象類型並不是 SqlSessionFactoryBean,而是 SqlSessionFactory。爲什麼呢?

查看 SqlSessionFactoryBean.java 源碼,我們可以在裏面找到以下方法:

    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }

    public Class<? extends SqlSessionFactory> getObjectType() {
        return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
    }

實際上,SqlSessionFactoryBean 實現了 FactoryBean接口,所以返回的不是 SqlSessionFactoryBean 的實例,而是它的 SqlSessionFactoryBean.getObject() 的返回值。

4.3 單例模式

Spring依賴注入Bean實例默認是單例的(當然,我們可以手動更改其爲非單例的)。

Spring的依賴注入都是發生在 AbstractBeanFactory 的 getBean 裏。getBean 的 doGetBean 方法調用 getSingleton 進行 bean 的創建。而 singleton 就是 Bean 作用域中的一個。使用 singleton 定義的 Bean 在 Spring 容器中只有一個 Bean 示例。

  • getBean() 調用 doGetBean()

    	@Override
    	public <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException {
    		return doGetBean(name, requiredType, null, false);
    	}
    
  • doGetBean() 調用 getSingleton 進行 bean 的創建。

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
      	 ……
         ……
    
             // Create bean instance.
             if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                   try {
                      return createBean(beanName, mbd, args);
                   }
                   catch (BeansException ex) {
                      // Explicitly remove instance from singleton cache: It might have been put there
                      // eagerly by the creation process, to allow for circular reference resolution.
                      // Also remove any beans that received a temporary reference to the bean.
                      destroySingleton(beanName);
                      throw ex;
                   }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
             }
    			……
      		……
       return (T) bean;
    }
    

5. AOP 的底層思想——代理模式

Spring 框架中實現 AOP 的底層就是動態代理模式的實現。Spring AOP 中常用 JDK 動態代理和 CGLIB 動態代理這兩種技術。Spring 中默認使用 JDK 動態代理實現 AOP 編程。其中用到一個非常關鍵的類叫做 ProxyFactoryBean。其作用是什麼呢?從上文我們知道切面是在應用運行的時刻被織入(所謂織入,就是把切面應用到目標對象並創建新的代理對象的過程)。一般情況下,在織入切面時,AOP 容器會爲目標對象的創建動態地創建一個代理對象,然後我們可以從 IoC 容器中獲取增強後的目標對象進行使用,這樣就實現了 AOP 編程。

6. Spring 框架中涉及到的其他設計模式

6.1 觀察者模式

Spring 的事件驅動模型使用的就是觀察者模式。Spring中觀察者模式使用的一個實例就是 Listener 的實現。

我們知道事件機制的實現需要三個部分:事件,事件監聽器,事件源。我們就從這三個部分入手分析。

  • ApplicationEvent 抽象類**[事件]**

    public abstract class ApplicationEvent extends EventObject {
        private static final long serialVersionUID = 7099057708183571937L;
        private final long timestamp = System.currentTimeMillis();
    
        public ApplicationEvent(Object source) {
            super(source);
        }
    
        public final long getTimestamp() {
            return this.timestamp;
        }
    }
    

    這個類繼承自 JDK 的 EventObject 類,並且通過構造器參數source得到事件源。該類的實現類 ApplicationContextEvent 表示ApplicaitonContext 的容器事件。

  • ApplicationListener 接口**[事件監聽器]**

    public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
        void onApplicationEvent(E var1);
    }
    

    繼承自jdk的EventListener,所有的監聽器都要實現這個接口。這個接口只有一個onApplicationEvent()方法,該方法接受一個ApplicationEvent或其子類對象作爲參數,在方法體中,可以通過不同對Event類的判斷來進行相應的處理。當事件觸發時所有的監聽器都會收到消息。

  • ApplicationContext 接口**[事件源]**

    這個接口即是我們所說的 IoC 容器,它不僅繼承了許多個工廠接口,它還實現了ApplicationEventPublisher 接口,可以充當事件源。

    public interface ApplicationEventPublisher {
        default void publishEvent(ApplicationEvent event) {
            this.publishEvent((Object)event);
        }
    
        void publishEvent(Object var1);
    }
    
6.2 策略模式

Spring框架的資源訪問是通過 Resource 接口來實現的 。Resource 接口本身沒有提供訪問任何底層資源的實現邏輯,針對不同的底層資源,Spring 將會提供不同的 Resource 實現類,不同的實現類負責不同的資源訪問邏輯。這就充分體現了策略模式。

6.3 模板方法模式

Spring 框架採用模板方法模式來實現以一種統一而集中的方式來處理資源的獲取和釋放。目前筆者學習到的體現模板方法模式的最典型的例子就是 Jdbc Template。從名字上看我們就可以看得出來這個一個 jdbc 的模板類。

public class JdbcTemplate {  
    public final Object execute(StatementCallback callback){  
        Connection con=null;  
        Statement stmt=null;  
        try{  
            con=getConnection();  
            stmt=con.createStatement();  
            Object retValue=callback.doWithStatement(stmt);  
            return retValue;  
        }catch(SQLException e){  
            ...  
        }finally{  
            closeStatement(stmt);  
            releaseConnection(con);  
        }  
    }  

    ...//其它方法定義  
}   
6.4 適配器模式

SpringMVC 中有一個適配器類叫做 HandlerAdapter。

public interface HandlerAdapter {
    boolean supports(Object var1);

    @Nullable
    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

前端發送請求給後端的時候,進入 SpringMVC 的前端控制器 DispatcherServlet。DispatcherServlet根據 HandlerMapping 返回的 handler,向 HandlerAdatper 發起請求,處理Handler。HandlerAdapter 根據規則找到對應的Handler並讓其執行,執行完畢後 Handler 會向 HandlerAdapter 返回一個 ModelAndView,最後由 HandlerAdapter 向DispatchServelet 返回一個 ModelAndView。

HandlerAdatper使得Handler的擴展變得容易,只需要增加一個新的Handler和一個對應的 HandlerAdapter 即可。因此 Spring 定義了一個適配接口,使得每一種 Controller 有一種對應的適配器實現類,讓適配器代替 Controller 執行相應的方法。這樣在擴展 Controller時,只需要增加一個適配器類就完成了 SpringMVC 的擴展了。

6.5 裝飾者模式

Spring中用到的裝飾者模式在類名上有兩種表現:一種是類名中含有Wrapper,另一種是類名中含有Decorator。其作用就是動態地給一個對象添加一些額外的職責。

image-20200612042905439

7. 結語

以上就是筆者對於 Spring 框架中涉及到的面向對象設計原則和設計模型的分析與討論。當然,Spring 框架設計到的設計思想遠不止這些,目前就筆者所學到的知識也只能是泛泛而談,但相信隨着對 Spring 框架學習的深入以及隨着項目經驗的積累,在未來會對其有更深入的理解和體會。


參考資料:

武漢大學王翀老師《軟件需求與建模相關課件》

《J2EE框架整合開發入門到實戰》(清華大學出版社)

https://blog.csdn.net/caoxiaohong1005/article/details/80039656

https://blog.csdn.net/bigtree_3721/article/details/51037547

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