關於Spring的IoC容器,你瞭解多少

IoC的基本概念

  Ioc的全稱是Inversion of Control,中文通常翻譯爲“控制反轉”。好萊塢原則“Dont't call us, we will call you.”,恰如其分表達了“反轉”的意味,是用來形容Ioc最多的一句話。

  IoC的理念就是,讓別人爲你服務。在下圖中,也就是讓IoC Service Provider來爲你服務!

  在通常情況下,被注入對象會直接依賴於被依賴對象。但是,在IoC場景中,二者之間是通過IoC Service Provider來打交道,所有的被注入對象和依賴對象現在由IoC Service Provider統一管理,被注入對象需要什麼,直接跟IoC Service Provider招呼一聲,後者就會把相應的被依賴對象注入到被注入對象中,從而達到IoC Service Provider爲被注入對象服務的目的。IoC Service Provider在這裏就是通常的IoC容器所充當的角色。從被注入對象的角度看,與之前直接尋求依賴對象相比,依賴對象的取得方式發生了反轉,控制也從被注入對象轉到了IoC Service Provider那裏。

  J2EE的一種核心模式就是通過引入中間代理者消除對象間複雜的耦合關係,並統一管理分散的複雜耦合關係。

IoC的實現有兩種:

  • DI(Dependence Injection),中文名稱叫依賴注入,對象和對象的屬性都是以注入的方式添加到容器中,Spring中使用的這種,本文也主要是講述該種實現。
  • DL(Dependence Lookup),中文名稱叫依賴查找,類似於目錄的查找方法。

Ioc容器的三種注入方式:

  • 構造方法注入:這種注入方式的優點就是,對象在構造完成後,即已進入就緒狀態,可以立即使用。缺點就是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而通過反射構造對象的時候,對相同類型的參數的處理會比較困難。而且在Java中,構造方法無法被繼承,無法設置默認值。對於非必須的依賴處理,可能需要引入多個構造方法,而參數數量的變動可能造成維護上的不便。
  • setter方法注入:因爲方法可以命名,所以setter方法注入在描述性上要比構造注入好一些。另外,setter方法可以被繼承,允許設置默認值。缺點就是對象無法在構造完成後馬上進入就緒狀態。
  • 接口注入:該方式已被棄用。因爲它強制被注入對象實現不必要的接口,帶有侵入性。

  如果要用一句話來概括IoC可以帶給我們什麼,那麼我希望是,IoC是一種可以幫助我們解耦各業務對象間依賴關係的對象綁定方式。

掌管大局的IoC Service Provider

IoC Service Provider的職責

  IoC Service Provider的職責相對來說比較簡單,主要有兩個:業務對象的構建管理和業務對象間的依賴綁定。

  • 業務對象的構建管理:在IoC場景中,業務對象無需關心所依賴的對象如何構建如何獲取得,但這部分工作始終都需要有人來做。所以,IoC Service Provider需要將對象的構建邏輯從客戶端對象哪裏剝離出來,以免這部分邏輯污染業務對象的實現。
  • 業務對象間的綁定:對於IoC Service Provider來說,這是它的最終使命之所在。如果不能完成這個職責,那麼,無論業務對象如何的“呼喊”,也不會得到依賴對象的任何響應(最常見的倒是會收到一個NullPointException)。IoC Service Provider通過結合之前構建和管理的所有業務對象,以及各個業務對象間可以識別的依賴關係,將這些對象所依賴的對象注入綁定,從而保證每個業務對象在使用的時候,可以處於就緒狀態。

IoC Service Provider如何管理對象間的依賴關係

  1. 直接編碼方式:在容器啓動之前,我們就可以通過程序編碼的方式將被注入對象和被依賴對象註冊到容器中,並明確他們互相之間的依賴注入關係。如下的僞代碼就演示了這樣一個過程:
IoContainer container = ...;
container.register(FXNewsProviderc.lass, new FXNewsProvider());
container.register(IFXNewsListener.class, new DownJonsNewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newsProvider.getAndPersisNews();
  1. 配置文件方式:這是一種較爲普遍的依賴注入關係管理方式。像普通文本文件、properties文件、XML文件等,都可以成爲管理依賴注入關係的載體。對於如上的僞代碼示例來說,我們也可以使用Spring 配置文件的方式來配置和管理各個對象間的依賴關係。
<bean id="newsProvider" class="...FXNewsProvider">
  <property name="newsListener">
    <ref bean="djNewsListener" />
  </property>
  <property name="newPersister">
    <ref bean="djNewsPersister" />
  </property>
</bean>

<bean id="djNewsListener" class="...impl.DownJonesListener" />
<bean id="djNewsPersister" class="...impl.DownJonesPersister" />

最後,從讀取配置文件完成對象組裝的容器中的獲取FXNewsProvider並使用:

...
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider");
newsProvider.getAndPersisNews();
  1. 元數據方式:這種方式的代表實現是Google Guice,這是Bob Lee在Java 5的註解和Generic的基礎上開發的一套IoC框架。我們可以直接在類中使用元數據信息來標註各個對象間的依賴關係,然後Guice框架根據這些註解所提供的信息將這些對象組裝後,交給客戶端使用。

Spring的IoC容器之BeanFactory

  我們前面說過,Spring的IoC容器是一個IoC Service Provider,但是,這只是她被冠以IoC之名的部分原因,我們不能忽略的是"容器"。Spring的IoC容器和IoC Service Provider所提供的服務之間存在一定的交集,而這個關係如下圖:

Spring提供了兩種容器類型:

  • BeanFactory:基礎類型的IoC容器,提供完整的IoC服務支持,如果沒有特殊指定,默認採用延遲加載策略(lazy-load)。只有當客戶端對象需要訪問容器中某個受管理對象的時候,纔對該受管理對象進行初始化以及依賴注入操作。所以,相對來說,容器啓動初期速度較快,所需要的資源有限。對於資源有限,並且功能要求不是很嚴格的場景,BeanFactory是比較合適的IoC容器選擇。
  • ApplicationContext:ApplicationContext在BeanFactory的基礎上構建,是相對比較高級的容器實現,除了擁有BeanFactory的所有支持,ApplicationContext還提供了其它高級特性,比如事件發佈、國際化信息支持等。ApplicationContext所管理的對象,在該類型容器啓動後,默認全部初始化並綁定完成。所以,相對於BeanFactory來說,ApplicationContext要求更多的系統資源,同時,因爲在啓動時就完成所有初始化,容器啓動時間較之BeanFactory也會長一些。

通過下圖,我們可以對BeanFactory和ApplicationContext之間的關係有個更清晰的認知:

  BeanFactory,顧名思義,就是生產Bean的工廠。作爲Spring提供的基本IoC容器,BeanFactory可以完成作爲IoC Service Provider的所有職責,包括業務對象的註冊和對象間依賴關係的綁定。

BeanFactory的對象註冊和依賴綁定方式

  BeanFactory作爲一個IoC Service Provider爲了能夠明確管理各個業務對象及業務對象之間依賴綁定關係,同樣需要某種途徑來記錄和管理這些信息。

1.直接編碼方式
   public static void main(String[] args) {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory container = bindViaCode(beanRegistry);
        FXNewsProvider djNewsProvider = (FXNewsProvider) container.getBean("djNewsProvider");
        djNewsProvider.getAndPersisNews();
    }

    private static BeanFactory bindViaCode(DefaultListableBeanFactory registry) {
        AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class);
        AbstractBeanDefinition newsListener = new RootBeanDefinition(DownJonesNewsListener.class);
        AbstractBeanDefinition newsPersister = new RootBeanDefinition(DownJonesNewsPersister.class);

        // 將bean定義註冊到容器中
        registry.registerBeanDefinition("djNewsProvider", newsProvider);
        registry.registerBeanDefinition("djListener", newsListener);
        registry.registerBeanDefinition("djPersister", newsPersister);

        // 指定依賴關係
        // 1. 可以通過構造方法注入方式
        ConstructorArgumentValues argValues = new ConstructorArgumentValues();
        argValues.addIndexedArgumentValue(0, newsListener);
        argValues.addIndexedArgumentValue(1, newsPersister);
        newsProvider.setConstructorArgumentValues(argValues);

        // 2.或者通過setter方法注入方式。該方式基於無參構造
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.addPropertyValue("newsListener", newsListener);
        propertyValues.addPropertyValue("newsPersister", newsPersister);
        newsProvider.setPropertyValues(propertyValues);

        // 綁定完成
        return registry;
    }

  BeanFactory只是一個接口,我們最終需要一個該接口的實現類來進行實際的Bean管理。DefaultListableBeanFactory就是這麼一個比較通用的BeanFactory實現類。DefaultListableBeanFactory除了間接地實現了BeanFactory接口,還實現了BeanDefinitionRegistry接口,該接口才是在BeanFacotry的實現中擔當Bean的註冊管理的角色。基本上,BeanFacotry接口只定義如何訪問容器內管理的Bean方法,各個BeanFacotry的具體實現類負責具體Bean的註冊及管理工作。BeanDefinitionRegistry接口定義抽象了Bean的註冊邏輯。通常情況下,具體的BeanFacotry實現類會實現這個接口來管理Bean的註冊。它們之間的關係如下圖所示:

  每一個受管的對象,在容器中都會有一個BeanDefinition的實例與之相對應,該BeanDefinition的實例負責保存對象的所有必要信息,包括其對應的對象的class類型,是否是抽象類,構造方法參數以及其他屬性等。當客戶端向BeanFactory請求相應對象的時候,BeanFactory會通過這些信息爲客戶端返回一個完備可用的對象實例,RootBeanDefinition和ChildBeanDefinition是BeanDefinition的兩個主要實現類。

2.外部配置文件方式

  Spring的IoC容器支持兩種配置文件格式:Properties文件格式和XML文件格式。採用外部配置文件時,Spring的IoC容器有一個統一的處理方式。通常情況下,需要根據不同的外部配置文件格式,給出相應的BeanDefinitionReader實現類,由BeanDefinitionReader的相應的實現類負責將相應的配置文件內容讀取並映射到BeanDefinition,然後將映射後的BeanDefinition註冊到一個BeanDefinitionRegistry,之後,BeanDefinitionRegistry即完成Bean的註冊和加載。當然,大部分工作,包括解析文件格式、裝配BeanDefinition之類的工作,都是由BeanDefinitionReader的相應實現類來做的,BeanDefinitionRegistry只不過負責保管而已。

3.註解方式

  在Spring 2.5發佈之後,Spring框架開始正式支持基於註解方式的依賴注入。如果不使用classpath-scanning功能的話,任然部分依賴於”基於XML配置文件“的依賴注入方式。

容器背後的祕密

  Spring的IoC容器所起的作用,就如下圖所示,它會以某種方式加載Configuration Metadate(通常也就是XML格式的配置信息),然後根據這些信息綁定整個系統的對象,最終組裝成一個可用的基於輕量級容器的應用系統。

  Spring的IoC容器所實現以上功能,基本上可以按照類似的流程劃分爲兩個階段,即容器啓動階段和Bean的實例化階段。如下圖所示:

  1. 容器啓動階段
      容器啓動伊始,首先會通過某種途徑加載Configuration Metadate。除了代碼方式比較直接,在大部分情況下,容器需要依賴某些工具類(BeanDefinitionReader)對加載的Configuration Metadate進行解析和分析,並將分析後的信息編組爲相應的BeanDefinition,最後把這些保存了bean定義必要信息的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器的啓動的工作就完成了。如下圖演示了這個階段的主要工作:

  總地來說,該階段所做的工作可以認爲是準備性的,重點更加側重於對象管理信息的收集。當然,一些驗證性或者輔助性的工作也可以在這個階段完成。
2. Bean實例化階段
  經過第一階段,現在所有的bean定義信息通過BeanDefinition的方式註冊到了BeanDefinitionRegistry中。當某個請求方通過容器的getBean()方法明確請求某個對象,或者因依賴關係容器需要隱式調用getBean()方法時,就會觸發第二階段的活動。
  該階段,容器會先檢查所請求的對象之前是否已經初始化。如果沒有,則會根據註冊的BeanDefinition所提供的信息實例化被請求對象,併爲其注入依賴。如果該對象實現了某些回調接口,也會根據回調接口的要求來裝配它。當該對象裝配完畢後,容器會立即將其返回給請求方使用。如果說第一階段只是根據圖紙裝配生產線的話,那麼第二階段就是使用裝配好的生產線來生產具體的產品了。

插手”容器的啓動“

  Spring 提供了一種叫做BeanFactoryPostProcessor的容器擴展機制,該機制允許我們在容器實例化相應對象之前,對註冊到容器的BeanDefinition所保存的信息進行相應的修改。這就相當於在容器實現的第一階段最後加入一道工序,讓我們對最終的BeanDefinition做一些額外的操作,比如修改其中bean定義的某些屬性,爲bean定義增加其他信息等。

  如果要自定義實現BeanFactoryPostProcessor,通常我們需要實現BeanFactoryPostProcessor接口。同時,因爲一個容器可能擁有多個BeanFactoryPostProcessor,這個時候我們可能需要實現類同時實現org.springframework.core.Ordered接口。以保證各個BeanFactoryPostProcessor可以按照預先設定的順序執行。但是,因爲Spring提供了幾個現成的BeanFactoryPostProcessor實現類,所以,大多時候,我們很少自己去實現某個BeanFactoryPostProcessor。其中,PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是兩個比較常用的BeanFactoryPostProcessor。另外,爲了處理配置文件中的數據類型與真正的業務對象所定義的數據類型轉換,Spring還允許我們通過CustomEditorConfigurer來註冊自定義的PropertyEditor來補助容器中默認的PropertyEditor。

  1. PropertyPlaceholderConfigurer
      通常情況下,我們不想將類似於系統管理相關的信息同業務對象相關的配置信息混雜到XML配置文件中,以免部署或者維護期間因爲改動繁雜的XML配置文件而出現問題。我們會將一些數據庫連接、郵件服務器等相關信息配置單獨配置到一個properties文件中,這樣,如果因系統資源變動的話,只需要關注這些簡單的properties配置文件即可。
      PropertyPlaceholderConfigurer允許我們在XML配置文件中使用佔位符,並將這些佔位符所代表的資源單獨配置到簡單的properties文件中來加載。PropertyPlaceholderConfigurer不單會從其配置的properties文件中加載配置項,同時還會檢查System類中的Properties,可以通過setSystemPropertiesMode()或者setPropertiesModeName()來控制是否加載或覆蓋System相應的Properties的行爲。

  2. PropertyOverrideConfigurer
      可以通過PropertyOverrideConfigurer對容器中配置的任何你想處理的bean定義的property信息進行覆蓋替換。當容器中配置多個PropertyOverrideConfigurer對同一個bean定義的同一個property值進行處理的時候,只有最後一個將會生效。

  3. CustomEditorConfigurer
      其它兩個BeanFactoryPostProcessor都是通過對BeanDefinition中的數據進行變更以達到某種目的。與它們有所不同的是,CustomEditorConfigurer是另一種類型的BeanFactoryPostProcessor實現,它只是輔助性地將後期會用到的信息註冊到容器,對BeanDefinition沒有做任何變動。

  我們知道,不管對象是什麼類型,也不管這些對象所聲明的依賴對象是什麼類型,通常都是通過XML(properties甚至其它媒介)文件格式來配置這些對象類型。但XML所記載的,都是String類型,即容器從XML格式的文件中讀取的都是字符串形式,最終應用到程序卻是由各種類型的對象所構成。要想完成這種由字符串到具體對象的轉換,都需要這種轉換規則相關的信息,而CustomEditorConfigurer就是幫助我們來傳達類似信息的。

  Spring內部通過JavaBean的PropertyEditor來幫助進行String類型到其它類型的轉換工作。只要爲每種對象類型提供一個PropertyEditor,就可以根據該對象類型取得與其相對應的PropertyEditor來做具體的類型轉換。Spring容器內部在做具體的類型轉換的時候,會採用JavaBean框架內默認的PropertyEditor搜尋邏輯。同時,Spring框架還提供了自身實現的一些PropertyEditor。這些PropertyEditor大部分都位於org.springframework.beans.propertyeditors包下。以下是這些Spring提供的部分PropertyEditor的簡要說明:

  • StringArrayPropertyEditor:該PropertyEditor會將負荷CSV格式的字符串轉換成String[]數組形式,默認是以逗號分隔字符串。與此類似的還有ByteArrayPropertyEditor、CharArrayPropertyEditor等都屬於類型功能的PropertyEditor。
  • ClassEditor:根據String類型的Class名稱,直接將其轉換成相應的Class對象,相當於通過Class.forName()完成的功效。
  • FileEditor:Spring提供的對應java.io.File類型的PropertyEditor,同屬於對資源進行定位的PropertyEditor還有InputStreamEditor、URLEditor等。
  • LocaleEditor:針對java.util.Locale類型的PropertyEditor。
  • PatternEditor:針對java.util.regex.Pattern的PropertyEditor。
  • 自定義PropertyEditor
    1. 直接實現PropertyEditor接口,不過,通常情況下,我們可以直接繼承PropertyEditorSupport類,以避免事項PropertyEditor接口中的所有方法。如果僅僅是支持單向的從String到相應對象類型的轉換,只要覆寫方法setAsText()即可,如果像支持雙向轉換,需要同時考慮getAsText()方法的覆寫。
    2. 通過CustomEditorConfigurer註冊自定義的PropertyEditor。Spring 2.0之前通常是通過CustomEditorConfigurer的customEditors屬性來指定自定義的PropertyEditor。2.0之後,比較提倡使用propertyEditorRegistrars屬性來指定自定義的PropertyEditor。不過,這樣我們就需要再多做一步工作,就是給出一個PropertyEditorRegistrar的實現。
瞭解bean的一生

  在已經可以藉助於BeanFactoryPostProcessor來干預Magic實現的第一個階段(容器啓動階段)的活動之後,我們就可以開始探索下一個階段,即bean實例化階段的實現邏輯了。
  容器啓動之後,並不會馬上就實例化相應的bean定義。我們知道,容器現在僅僅擁有所有對象的BeanDefinition來保存實例化階段將要用的必要信息。只要當請求方通過BeanFactory的getBean()方法請求某個對象實例的時候,纔有可能觸發Bean實例化階段的活動。BeanFactory的getBean()方法可以被客戶端對象顯示調用,也可以在容器內部隱式地被調用。隱式調用有如下兩種情況:

  • 對於BeanFactory來說,對象實例化默認採用延遲初始化。通常情況下,當對象A被請求而需要第一次實例化的時候,如果它所依賴的對象B之前沒有被實例化,那麼容器會先實例化對象A依賴的對象。這時容器內部就會首先實例化對象B,以及對象A依賴的其它還沒有被實例化的對象。這種情況是容器內部調用getBean(),對於本次請求的請求方是隱式的。
  • ApplicationContext啓動之後就會實例化所有的bean定義,但ApplicationContext在實現的過程中依然遵循Spring容器實現流程的兩個階段。只不過它會在啓動階段的活動完成之後,緊接着調用註冊到該容器的所有bean定義的實例化方法getBean()。這就是爲什麼當你得到ApplicationContext類型的容器引用時,容器內所有對象已經被全部被實例化完成。不信你查一下org.springframework.context.support.AbstractApplicationContext的refresh()方法。

  之所以說getBean()方法是有可能觸發Bean實例化階段的活動,是因爲只有當對應某個bean定義的getBean()方法第一次被調用時,不管是顯示的還是隱式的,Bean實例化階段的活動纔會被觸發,第二次調用則會直接返回容器緩存的第一次實例化完的對象實例(prototype類型bean除外)。當getBean()方法內部發現該bean定義之前還沒有被實例化之後,會通過createBean()進行具體的對象實例化,實例化過程如下:

提示: 可以在org.springframework.beans.factory.support.AbstractBeanFactory類的代碼中查看到getBean()方法的完整代碼實現邏輯,可以在其子類org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory的代碼中一窺createBean()方法的全貌。

  Spring容器將其所管理的對象全部給予統一的生命週期管理,這些被管理的對象拜託了原來那種”new完後被使用,脫離作用域後被收回“的命運。下面我們將詳細看一看每個bean在容器中是如何走過一生的。

  1. Bean的實例化與BeanWrapper
      容器在內部實例化的時候,採用“策略模式”來決定採用何種方式初始化bean實例。通常,可以通過反射或者cglib動態字節碼生成來初始化相應的bean實例或者動態生成其子類。InstantiationStrategy是實例化策略的抽象接口,其直接子類SimpleInstantiationStrategy實現了簡單的對象實例化功能,可以通過反射來實例化對象實例,但不支持方法注入方式的對象實例化。CglibSubclassingInstantiationStrategy繼承了SimpleInstantiationStrategy的以反射方式實例化對象的功能,並且通過cglib的動態字節碼生成功能,該策略實現類可以動態生成某個類的子類,進而滿足了方法注入所需的對象實例化需求,默認情況下,容器內部採用的是CglibSubclassingInstantiationStrategy。
      容器只要根據bean定義的BeanDefinition取得實例化信息,結合CglibSubclassingInstantiationStrategy以及不同的bean定義類型,就可以返回實例化完成的對象實例。但是,不是直接返回構造完成的對象實例,而是返回相應的BeanWrapper實例,它有一個實現類BeanWrapperImpl,其作用是對某個bean進行“包裹”,然後對這個“包裹”的bean進行操作,比如設置或獲取bean的響應屬性值。

  2. 各色的Aware接口
      當對象實例化完成並且相關屬性以及依賴設置完成後,spring容器會檢查當前對象實例是否實現了一系列的以Aware命名結尾的接口定義。如果是,則將這些Aware接口定義中規定的依賴注入給當前對象實例。常見的Aware接口有如下幾個:BeanNameAware,BeanClassLoaderAware,BeanFactoryAware。這幾個Aware接口只是針對BeanFactory類型的容器而言,對於ApplicationContext類型容器而言,也存在幾個Aware相關接口,如ApplicationContextAware,ResourceLoaderAware,ApplicationEventPublisherAware,MessageSourceAware接口,不過檢測這些接口並設置相關依賴的實現使用的是BeanPostProcessor。

  3. BeanPostProcessor
      BeanPostProcessor和BeanFactoryPostProcessor的概念容易混淆,但只要記住BeanPostProcessor是存在於對象實例化階段,而BeanFactoryPostProcessor是存在於容器啓動階段,則很好區分了。
      通常比較常見的使用BeanPostProcessor場景,是處理標記接口實現類,或者爲當前對象提供代理實現。上面提到的ApplicationContext對應的那些Aware接口實際上就是通過BeanPostProcessor的方式進行處理的。

  4. InitializingBean和init-method
      InitializingBean是容器內部廣泛使用的的一個對象生命週期標識接口,其定義如下:

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

  該接口的定義很簡單,其作用在於,在對象實例化過程中調用過“BeanPostProcessor”的前置處理後,會接着檢測當前對象是否實現了InitializingBean接口,如果是,則調用afterPropertiesSet()方法進一步調整對象實例的狀態。由於讓我們的業務對象去實現該接口,會顯得Spring容器比較具有侵入性,所以,Spring還提供了通過配置bean的init-method屬性來自定義對象的初始化操作。

  1. DisposableBean和destroy-method
      與InitializingBean和init-method用於對象的自定義初始化相對應,DisposableBean和destroy-method爲對象提供了執行自定義銷燬邏輯的機會。最常見到的該功能的使用場景就是pring容器中註冊數據庫連接池,在系統退出後,連接池應該關閉,以釋放相應資源。

Spring ioC容器之ApplicationContext

  作爲Spring提供的較之BeanFactory更爲先進的IoC容器,ApplicationContext除了擁有BeanFactory支持的所有功能之外,還進一步擴展了基本容器的功能,包括BeanFactoryPostProcessor,BeanPostProcessor以及其他特殊類型的bean的自動識別、容器啓動後bean實例的自動初始化、國際化信息支持、容器內事件發佈等。上一小節說明了ApplicationContext所支持的大部分功能,下面主要圍繞ApplicationContext較之BeanFactory特有的一些特性展開討論,即國際化信息支持、統一資源加載策略以及容器內事件發佈等。

統一資源加載策略

  Spring框架內部使用Resource接口作爲所有資源的抽象的訪問接口。現在有了資源,但如何去查找和定位這些資源,則應該是ResourceLoader的職責了,ResourceLoader接口是資源查找定位策略的統一抽象,具體的資源查找定位策略則由相應的ResourceLoader實現類給出。或許把ResourceLoader稱作統一資源定位器更恰當一些。ResourcePatternResolver——批量查找的ResourceLoader。ResourcePatternResolver是ResourceLoader的擴展,ResourceLoader每次只能根據資源路徑返回確定的單個Resource實例,而ResourcePatternResolver則可以根據指定的資源路徑匹配模式,每次返回多個Resource實例。

  由於ApplicaitonContext繼承了ResourcePatternResolver,當然就間接實現了ResourceLoader接口。所以任何的ApplicaitonContext實現都可以看作是一個ResourceLoader甚至ResourcePatternResolver,而這就是ApplicaitonContext支持Spring內統一資源加載策略的真相。

國際化信息支持

  1. Java SE提供的國際化支持
      程序的國際化不是三言兩語可以講清楚的,它涉及許多內容,如貨幣的格式、時間的表現形式、各國家和地區的語言文字等。對於Java中的國際化信息處理,主要涉及兩個類,即Locale和ResourceBundle。不同的Locale代表不同的國家和地區,每個國家和地區在Locale這裏都有相應的簡寫代碼表示,包括語言代碼及國家代碼,這些代碼是ISO標準代碼。ResourceBundle用來保存特定於某個Locale的信息,通常,ResourceBundle管理一組信息序列,所有的信息序列都有一個統一的basename,然後特定的Locale的信息,可以根據basename後追加的語言或地區代碼來區分。
  2. MessageSource和ApplicationContext
      Spring在Java SE的國際化支持基礎上,進一步抽象了國際化信息的訪問接口,也就是MessageSource,該接口的定義如下:
public interface MessageSource {
   @Nullable
   String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

   String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

   String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

  現在我們知道ApplicationContext除了實現了ResourceLoader以支持統一的資源加載,它還實現了MessageSource接口,那麼就跟ApplicationContext因爲實現了ResourceLoader而可以當作ResourceLoader來使用一樣,ApplicationContext現在也是一個MessageSource了。

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