1. 程式人生 > >Spring框架設計理念與設計模式

Spring框架設計理念與設計模式

Spring作為現在最優秀的框架之一,已被廣泛的使用,51CTO也曾經針對Spring框架中的JDBC應用做過報道。本文將從另外一個視角試圖剖析出Spring框架的作者設計Spring框架的骨骼架構的設計理念,有那幾個核心元件?為什麼需要這些元件?它們又是如何結合在一起構成Spring的骨骼架構?Spring的AOP特性又是如何利用這些基礎的骨骼架構來工作的?Spring中又使用了那些設計模式來完成它的這種設計的?它的這種 設計理念對對我們以後的軟體設計有何啟示?本文將詳細解答這些問題。

Spring的骨骼架構

Spring總共有十幾個元件,但是真正核心的元件只有幾個,下面是Spring框架的總體架構圖:

Spring框架的總體架構圖
圖1.Spring框架的總體架構圖

從上圖中可以看出Spring框架中的核心元件只有三個:Core、Context和Beans。它們構建起了整個Spring的骨骼架構。沒有它們就不可能有AOP、Web等上層的特性功能。下面也將主要從這三個元件入手分析Spring。

Spring的設計理念

前面介紹了Spring的三個核心元件,如果再在它們三個中選出核心的話,那就非Beans元件莫屬了,為何這樣說,其實Spring就是面向Bean的程式設計(BOP,Bean Oriented Programming),Bean在Spring 中才是真正的主角。

Bean在Spring中作用就像Object對OOP的意義一樣,沒有物件的概念就像沒有面向物件程式設計,Spring中沒有Bean也就沒有Spring存在的意義。就像一次演出舞臺都準備好了但是卻沒有演員一樣。為什 麼要Bean這種角色Bean或者為何在Spring如此重要,這由Spring框架的設計目標決定,Spring為何如此流行,我們用Spring的原因是什麼,想想你會發現原來Spring解決了一個非常關鍵的問題他可以讓 你把物件之間的依賴關係轉而用配置檔案來管理,也就是他的依賴注入機制。而這個注入關係在一個叫Ioc容器中管理,那Ioc容器中有又是什麼就是被Bean包裹的物件。Spring正是通過把物件包裝在 Bean中而達到對這些物件管理以及一些列額外操作的目的。

它這種設計策略完全類似於Java實現OOP的設計理念,當然了Java本身的設計要比Spring複雜太多太多,但是都是構建一個資料結構,然後根據這個資料結構設計他的生存環境,並讓它在這個環境中 按照一定的規律在不停的運動,在它們的不停運動中設計一系列與環境或者與其他個體完成資訊交換。這樣想來回過頭想想我們用到的其他框架都是大慨類似的設計理念。

核心元件如何協同工作

前面說Bean是Spring中關鍵因素,那Context和Core又有何作用呢?前面吧Bean比作一場演出中的演員的話,那Context就是這場演出的舞臺背景,而Core應該就是演出的道具了。只有他們在一起才能 具備能演出一場好戲的最基本的條件。當然有最基本的條件還不能使這場演出脫穎而出,還要他表演的節目足夠的精彩,這些節目就是Spring能提供的特色功能了。

我們知道Bean包裝的是Object,而Object必然有資料,如何給這些資料提供生存環境就是Context要解決的問題,對Context來說他就是要發現每個Bean之間的關係,為它們建立這種關係並且要維護好 這種關係。所以Context就是一個Bean關係的集合,這個關係集合又叫Ioc容器,一旦建立起這個Ioc容器後Spring就可以為你工作了。那Core元件又有什麼用武之地呢?其實Core就是發現、建立和維護每 個Bean之間的關係所需要的一些列的工具,從這個角度看來,Core這個元件叫Util更能讓你理解。

它們之間可以用下圖來表示:

三個元件關係
圖2.三個元件關係

核心元件詳解

這裡將詳細介紹每個元件內部類的層次關係,以及它們在執行時的時序順序。我們在使用Spring是應該注意的地方。

Bean元件

前面已經說明了Bean元件對Spring的重要性,下面看看Bean這個元件式怎麼設計的。Bean元件在Spring的org.springframework.beans包下。這個包下的所有類主要解決了三件事:Bean的定義、Bean 的建立以及對Bean的解析。對Spring的使用者來說唯一需要關心的就是Bean的建立,其他兩個由Spring在內部幫你完成了,對你來說是透明的。

SpringBean的建立時典型的工廠模式,他的頂級介面是BeanFactory,下圖是這個工廠的繼承層次關係:

Bean工廠的繼承關係 
圖4.Bean工廠的繼承關係

BeanFactory有三個子類:ListableBeanFactory、HierarchicalBeanFactory和Autowire Capable Bean Factory。但是從上圖中我們可以發現最終的預設實現類是DefaultListableBeanFactory,他實 現了所有的介面。那為何要定義這麼多層次的介面呢?查閱這些介面的原始碼和說明發現,每個介面都有他使用的場合,它主要是為了區分在Spring內部在操作過程中物件的傳遞和轉化過程中,對物件的 資料訪問所做的限制。例如ListableBeanFactory介面表示這些Bean是可列表的,而HierarchicalBeanFactory表示的是這些Bean是有繼承關係的,也就是每個Bean有可能有父Bean。 AutowireCapableBeanFactory介面定義Bean的自動裝配規則。這四個介面共同定義了Bean的集合、Bean之間的關係、以及Bean行為。

Bean的定義主要有BeanDefinition描述,如下圖說明了這些類的層次關係:

Bean定義的類層次關係圖
圖5.Bean定義的類層次關係圖

Bean的定義就是完整的描述了在Spring的配置檔案中你定義的節點中所有的資訊,包括各種子節點。當Spring成功解析你定義的一個節點後,在Spring的內部他就被轉化 成BeanDefinition物件。以後所有的操作都是對這個物件完成的。

Bean的解析過程非常複雜,功能被分的很細,因為這裡需要被擴充套件的地方很多,必須保證有足夠的靈活性,以應對可能的變化。Bean的解析主要就是對Spring配置檔案的解析。這個解析過程主要通過 下圖中的類完成:

Bean的解析類
圖6.Bean的解析類

當然還有具體對tag的解析這裡並沒有列出。

Context元件

Context在Spring的org.springframework.context包下,前面已經講解了Context元件在Spring中的作用,他實際上就是給Spring提供一個執行時的環境,用以儲存各個物件的狀態。下面看一下這個 環境是如何構建的。

ApplicationContext是Context的頂級父類,他除了能標識一個應用環境的基本資訊外,他還繼承了五個介面,這五個介面主要是擴充套件了Context的功能。下面是Context的類結構圖:

Context相關的類結構圖
圖7.Context相關的類結構圖

從上圖中可以看出ApplicationContext繼承了BeanFactory,這也說明了Spring容器中執行的主體物件是Bean,另外ApplicationContext繼承了ResourceLoader介面,使得ApplicationContext可以訪 問到任何外部資源,這將在Core中詳細說明。

ApplicationContext的子類主要包含兩個方面:

ConfigurableApplicationContext表示該Context是可修改的,也就是在構建Context中使用者可以動態新增或修改已有的配置資訊,它下面又有多個子類,其中最經常使用的是可更新的Context,即 AbstractRefreshableApplicationContext類。

WebApplicationContext顧名思義,就是為web準備的Context他可以直接訪問到ServletContext,通常情況下,這個介面使用的少。

再往下分就是按照構建Context的檔案型別,接著就是訪問Context的方式。這樣一級一級構成了完整的Context等級層次。

總體來說ApplicationContext必須要完成以下幾件事:

◆標識一個應用環境

◆利用BeanFactory建立Bean物件

◆儲存物件關係表

◆能夠捕獲各種事件

Context作為Spring的Ioc容器,基本上整合了Spring的大部分功能,或者說是大部分功能的基礎。

Core元件

Core元件作為Spring的核心元件,他其中包含了很多的關鍵類,其中一個重要組成部分就是定義了資源的訪問方式。這種把所有資源都抽象成一個介面的方式很值得在以後的設計中拿來學習。下面就 重要看一下這個部分在Spring的作用。

下圖是Resource相關的類結構圖:

Resource相關的類結構圖
圖8.Resource相關的類結構圖

從上圖可以看出Resource介面封裝了各種可能的資源型別,也就是對使用者來說遮蔽了檔案型別的不同。對資源的提供者來說,如何把資源包裝起來交給其他人用這也是一個問題,我們看到Resource 介面繼承了InputStreamSource介面,這個介面中有個getInputStream方法,返回的是InputStream類。這樣所有的資源都被可以通過InputStream這個類來獲取,所以也遮蔽了資源的提供者。另外還有一 個問題就是載入資源的問題,也就是資源的載入者要統一,從上圖中可以看出這個任務是由ResourceLoader介面完成,他遮蔽了所有的資源載入者的差異,只需要實現這個介面就可以載入所有的資源, 他的預設實現是DefaultResourceLoader。

下面看一下Context和Resource是如何建立關係的?首先看一下他們的類關係圖:

Context和Resource的類關係圖
圖9.Context和Resource的類關係圖

從上圖可以看出,Context是把資源的載入、解析和描述工作委託給了ResourcePatternResolver類來完成,他相當於一個接頭人,他把資源的載入、解析和資源的定義整合在一起便於其他元件使用。 Core元件中還有很多類似的方式。

Ioc容器如何工作

前面介紹了Core元件、Bean元件和Context元件的結構與相互關係,下面這裡從使用者角度看一下他們是如何執行的,以及我們如何讓Spring完成各種功能,Spring到底能有那些功能,這些功能是如 何得來的,下面介紹。

如何建立BeanFactory工廠

正如圖2描述的那樣,Ioc容器實際上就是Context元件結合其他兩個元件共同構建了一個Bean關係網,如何構建這個關係網?構建的入口就在AbstractApplicationContext類的refresh方法中。這個方 法的程式碼如下:

清單1.AbstractApplicationContext.refresh

  1. public void refresh() throws BeansException, IllegalStateException {  
  2.     synchronized (this.startupShutdownMonitor) {  
  3.         // Prepare this context for refreshing.  
  4.         prepareRefresh();  
  5.         // Tell the subclass to refresh the internal bean factory.  
  6.         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  
  7.         // Prepare the bean factory for use in this context.  
  8.         prepareBeanFactory(beanFactory);  
  9.         try {  
  10.             // Allows post- processing of the bean factory in context subclasses.  
  11.             postProcessBeanFactory(beanFactory);  
  12.             // Invoke factory processors registered as beans in& nbsp;the context.  
  13.             invokeBeanFactoryPostProcessors(beanFactory);  
  14.             // Register bean processors that intercept bean crea tion.  
  15.             registerBeanPostProcessors (beanFactory);  
  16.             // Initialize message source for this context.  
  17.             initMessageSource();  
  18.             // Initialize event multicaster for this context.  
  19.             initApplicationEventMulticaster();  
  20.             // Initialize other special beans in specific contex t subclasses.  
  21.             onRefresh();  
  22.             // Check for listener beans and register them.  
  23.             registerListeners();  
  24.             // Instantiate all remaining (non-lazy-init) singletons.  
  25.             finishBeanFactoryInitialization (beanFactory);  
  26.             // Last step: publish corresponding event.  
  27.             finishRefresh();  
  28.         }  
  29.         catch (BeansException ex) {  
  30.             // Destroy already created singletons to avoid dangl ing resources.  
  31.             destroyBeans();  
  32.             // Reset 'active' flag.  
  33.             cancelRefresh(ex);  
  34.             // Propagate exception to caller.  
  35.             throw ex;  
  36.         }  
  37.     }  
  38. }  

這個方法就是構建整個Ioc容器過程的完整的程式碼,瞭解了裡面的每一行程式碼基本上就瞭解大部分Spring的原理和功能了。

這段程式碼主要包含這樣幾個步驟:

◆構建BeanFactory,以便於產生所需的“演員”

◆註冊可能感興趣的事件

◆建立Bean例項物件

◆觸發被監聽的事件

下面就結合程式碼分析這幾個過程。

第二三句就是在建立和配置BeanFactory。這裡是refresh也就是重新整理配置,前面介紹了Context有可更新的子類,這裡正是實現這個功能,當BeanFactory已存在是就更新,如果沒有就新建立。下面是 更新BeanFactory的方法程式碼:

清單2. AbstractRefreshableApplicationContext. refreshBeanFactory

  1. protected final void refreshBeanFactory() throws BeansException {  
  2.     if (hasBeanFactory()) {  
  3.         destroyBeans();  
  4.         closeBeanFactory();  
  5.     }  
  6.     try {  
  7.         DefaultListableBeanFactory beanFactory = createBeanFactory();  
  8.         beanFactory.setSerializationId(getId());  
  9.         customizeBeanFactory(beanFactory);  
  10.         loadBeanDefinitions(beanFactory);  
  11.         synchronized (this.beanFactoryMonitor) {  
  12. this.beanFactory = beanFactory;  
  13.         }  
  14.     }  
  15.     catch (IOException ex) {  
  16.         throw new ApplicationContextException(  
  17.                        "I/O error& nbsp;parsing bean definition source for "  
  18.                        + getDisplayName (), ex);  
  19.     }  
  20. }  

這個方法實現了AbstractApplicationContext的抽象方法refreshBeanFactory,這段程式碼清楚的說明了BeanFactory的建立過程。注意BeanFactory物件的型別的變化,前 面介紹了他有很多子類,在什麼情況下使用不同的子類這非常關鍵。BeanFactory的原始物件是DefaultListableBeanFactory,這個非常關鍵,因為他設計到後面對這個物件的多種操作,下面看一下這個 類的繼承層次類圖:

DefaultListableBeanFactory類繼承關係圖
圖10.DefaultListableBeanFactory類繼承關係圖

從這個圖中發現除了BeanFactory相關的類外,還發現了與Bean的register相關。這在refreshBeanFactory方法中有一行loadBeanDefinitions(beanFactory)將找到答案,這個方法將開始載入、解析 Bean的定義,也就是把使用者定義的資料結構轉化為Ioc容器中的特定資料結構。

這個過程可以用下面時序圖解釋:

建立BeanFactory時序圖
圖11.建立BeanFactory時序圖

Bean的解析和登記流程時序圖如下:

解析和登記Bean物件時序圖
圖12.解析和登記Bean物件時序圖

建立好BeanFactory後,接下去新增一些Spring本身需要的一些工具類,這個操作在AbstractApplicationContext的prepareBeanFactory方法完成。

AbstractApplicationContext中接下來的三行程式碼對Spring的功能擴充套件性起了至關重要的作用。前兩行主要是讓你現在可以對已經構建的BeanFactory的配置做修改,後面一行就是讓你可以對以後再 建立Bean的例項物件時新增一些自定義的操作。所以他們都是擴充套件了Spring的功能,所以我們要學習使用Spring必須對這一部分搞清楚。

其中在invokeBeanFactoryPostProcessors方法中主要是獲取實現BeanFactoryPostProcessor介面的子類。並執行它的postProcessBeanFactory方法,這個方法的宣告如下:

清單3.BeanFactoryPostProcessor.postProcessBeanFactory

  1. void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)  
  2.     throws BeansException;  

它的引數是beanFactory,說明可以對beanFactory做修改,這裡注意這個beanFactory是ConfigurableListableBeanFactory型別的,這也印證了前面介紹的不同BeanFactory所使用的場合不同,這裡 只能是可配置的BeanFactory,防止一些資料被使用者隨意修改。

registerBeanPostProcessors方法也是可以獲取使用者定義的實現了BeanPostProcessor介面的子類,並執行把它們註冊到BeanFactory物件中的beanPostProcessors變數中。BeanPostProcessor中宣告 了兩個方法:postProcessBeforeInitialization、postProcessAfterInitialization分別用於在Bean物件初始化時執行。可以執行使用者自定義的操作。

後面的幾行程式碼是初始化監聽事件和對系統的其他監聽者的註冊,監聽者必須是ApplicationListener的子類。

如何建立Bean例項並構建Bean的關係網

下面就是Bean的例項化程式碼,是從finishBeanFactoryInitialization方法開始的。

清單4.AbstractApplicationContext.finishBeanFactoryInitialization

  1. protected void finishBeanFactoryInitialization(  
  2.         ConfigurableListableBeanFactory beanFactory) {  
  3.     // Stop using the temporary ClassLoader for type matching.  
  4.     beanFactory.setTempClassLoader(null);  
  5.     // Allow for caching all bean definition metadata, not expecting further changes .  
  6.     beanFactory.freezeConfiguration();  
  7.     // Instantiate all remaining (non-lazy-init) singletons.  
  8.     beanFactory.preInstantiateSingletons();  
  9. }  

從上面程式碼中可以發現Bean的例項化是在BeanFactory中發生的。preInstantiateSingletons方法的程式碼如下:

清單5.DefaultListableBeanFactory.preInstantiateSingletons

  1. public void preInstantiateSingletons() throws BeansException {  
  2.     if (this.logger.isInfoEnabled()) {  
  3.         this.logger.info("Pre- instantiating singletons in " + this);  
  4.     }  
  5.     synchronized (this.beanDefinitionMap) {  
  6.         for  (String beanName : this.beanDefinitionNames) {  
  7.             RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);  
  8.             if (!bd.isAbstract()  && bd.isSingleton()  
  9.                 && !bd.isLazyInit()) {  
  10.                 if  (isFactoryBean(beanName)) {  
  11.                     final FactoryBean factory =  
  12.                         (FactoryBean)  getBean(FACTORY_BEAN_PREFIX+ beanName);  
  13.