Spring詳解(四)——Spring IOC容器的設計與實現


1、Spring IOC容器的設計

我們知道,在Spring中實現控制反轉的是IoC容器,所以對於 IoC 來說,最重要的就是容器。因為容器管理着 Bean 的生命周期,控制着 Bean 的依賴注入。那么, 在Spring框架中是如何設計容器的呢?我們來看一下:Spring IoC 容器的設計主要是基於以下兩個接口:

  • 實現BeanFactory接口的簡單容器
  • 實現ApplicationContext接口的高級容器

這兩個容器間的關系如下圖:

image

通過上面的圖片我們可以發現ApplicationContext是BeanFactory的子接口。其中BeanFactory是Spring IoC容器的最底層接口,它只提供了IOC容器最基本的功能,給具體的IOC容器的實現提供了規范,所以我們稱它為簡單容器。它主要是負責配置、生產和管理bean,其內部定義了對單個bean的獲取,對bean的作用域判斷,獲取bean類型,獲取bean別名等功能。而ApplicationContext擴展(繼承)了BeanFactory,所以ApplicationContext包含BeanFactory的所有功能,同時它又繼承了MessageSource、ListableBeanFactory、ResourceLoader、ApplicationEventPublisher等接口,這樣ApplicationContext為BeanFactory賦予了更高級的IOC容器特性,我們稱它為高級容器。在實際應用中,一般不使用 BeanFactory,通常建議優先使用ApplicationContext(BeanFactory一般供代碼內部使用)。

注意:上面兩個重要的類都是接口,既然是接口那總得有具體的實現類吧,那是由哪個類來具體實現IOC容器的呢?答:在BeanFactory子類中有一個DefaultListableBeanFactory類,它實現了包含基本Spirng IoC容器所具有的重要功能,我們開發時不論是使用BeanFactory系列還是ApplicationContext系列來創建容器基本都會使用到DefaultListableBeanFactory類。在平時我們說BeanFactory提供了IOC容器最基本的功能和規范,但真正可以作為一個可以獨立使用的IOC容器還是DefaultListableBeanFactory,因為它真正實現了BeanFactory接口中的方法。所以DefaultListableBeanFactory 是整個Spring IOC的始祖,在Spring中實際上把它當成默認的IoC容器來使用。但是暫時我們不深入了解,只需知道有這么個東西即可。

2、BeanFactory和ApplicationContext的區別

通過上面的介紹我們知道,BeanFactory和ApplicationContext是Spring IOC容器的兩大核心接口,它們都可以當做Spring的容器。其中ApplicationContext是BeanFactory的子接口,那么它們兩者之間的區別在哪呢?下面我們來學習一下:

①、提供的功能不同:

BeanFactory:是Spring里面最底層的接口,它只提供了IOC容器最基本的功能,給具體的IOC容器的實現提供了規范。包含了各種Bean的定義,讀取bean配置文檔,管理bean的加載、實例化,控制bean的生命周期,維護bean之間的依賴關系等。

ApplicationContext:它作為BeanFactory的子接口,除了提供BeanFactory所具有的功能外,還提供了更完整的框架功能。我們看一下ApplicationContext類結構:

public interface ApplicationContext extends 
                            EnvironmentCapable,
                            ListableBeanFactory,
                            HierarchicalBeanFactory,
                            MessageSource,
                            ApplicationEventPublisher,
                            ResourcePatternResolver {

}

ApplicationContext額外提供的功能有:

  1. 支持國際化(MessageSource)
  2. 統一的資源文件訪問方式(ResourcePatternResolver)
  3. 提供在監聽器中注冊bean的事件(ApplicationEventPublisher)
  4. 同時加載多個配置文件
  5. 載入多個(有繼承關系)上下文 ,使得每一個上下文都專注於一個特定的層次,比如應用的web層(HierarchicalBeanFactory)

②、 啟動時的狀態不同:

BeanFactroy采用的是延遲加載形式來注入Bean的,即只有在使用到某個Bean時(調用getBean()),才對該Bean進行加載實例化。這樣,我們就不能發現一些存在的Spring的配置問題。如果Bean的某一個屬性沒有注入,BeanFacotry加載后,直至第一次使用調用getBean方法才會拋出異常。

ApplicationContext,它是在容器啟動時,一次性創建了所有的Bean。這樣,在容器啟動時,我們就可以發現Spring中存在的配置錯誤,這樣有利於檢查所依賴屬性是否注入。 ApplicationContext啟動后預載入所有的單實例Bean,通過預載入單實例bean ,確保當你需要的時候,你就不用等待,因為它們已經創建好了。相對於基本的BeanFactory,ApplicationContext 唯一的不足是占用內存空間。當應用程序配置Bean較多時,程序啟動較慢。


③、BeanFactory通常以編程的方式被創建,ApplicationContext還能以聲明的方式創建,如使用ContextLoader。

④、BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區別是:BeanFactory需要手動注冊,而ApplicationContext則是自動注冊。

3、BeanFactory容器的設計原理

我們知道,BeanFactory接口提供了使用IOC容器的基本規范,在這個基礎上,Spring還提供了符合這個IOC容器接口的一系列容器的實現供開發人員使用,我們以DefaultListableBeanFactory的子類XmlBeanFactory的實現為例,來說明簡單IOC容器的設計原理,下面的圖為BeanFactory——>XmlBeanFactory設計的關系,相關接口和實現類的圖如下:

image

可以發現它的體系很龐大,下面簡單介紹一下圖片中左邊重要的接口和類:

  • BeanFactory接口:是Spring IOC容器的最底層接口,提供了容器的基本規范,如獲取bean、是否包含bean、是否單例與原型、獲取bean類型和bean別名的方法。
  • HierarchicalBeanFactory:提供父容器的訪問功能,它內部定義了兩個方法。
  • ListableBeanFactory:提供了列出工廠中所有的Bean的方法 定義了容器內Bean的枚舉功能(枚舉出來的Bean不會包含父容器)。
  • AutowireCapableBeanFactory:在BeanFactory基礎上實現對已存在實例的管理,主要定義了集成其它框架的功能。一般應用開發者不會使用這個接口,所以像ApplicationContext這樣的外觀實現類不會實現這個接口,如果真想用可以通過ApplicationContext的getAutowireCapableBeanFactory接口獲取。
  • ConfigurableBeanFactory:定義了BeanFactory的配置功能。
  • ConfigurableListableBeanFactory:繼承了上述的所有接口,增加了其他功能:比如類加載器、類型轉化、屬性編輯器、BeanPostProcessor、作用域、bean定義、處理bean依賴關系、bean如何銷毀等功能。
  • DefaultListableBeanFactory:實現上述BeanFactory接口中所有功能。它還可以注冊BeanDefinition。
  • XmlBeanFactory :在Spring3.1之前使用,后面被標記為Deprecated,繼承自DefaultListableBeanFactory,增加了對Xml文件解析的支持。

通過上面的圖片可以發現XmlBeanFactory是BeanFactory體系中的最底層的實現類,我們知道BeanFactory的實現主要是由DefaultListableBeanFactory類完成,而XmlBeanFactory又繼承了DefaultListableBeanFactory類,所以說BeanFactory實現的最底層是XmlBeanFactory,這個類是Rod Johnson大佬在2001年就寫下的代碼,可見這個類應該是Spring的元老類了。由於那個時候沒有使用注解,都是使用XML文件來配置Spring,所以XmlBeanFactory繼承DefaultListableBeanFactory的目的就很明顯,我們從XmlBeanFactory這個類的名字上就可以猜到,它是一個與XML相關的BeanFactory,沒錯,XmlBeanFactory在父類的基礎上增加了對XML文件解析的支持,也就是說它是一個可以讀取XML文件方式定義BeanDefinition的IOC容器。

注意:這里說一下BeanDefinition:在Spring中BeanDefinition非常的重要,從字面意思就知道它跟Bean的定義有關。它是對 IOC容器中管理的對象依賴關系的數據抽象,是IOC容器實現控制反轉功能的核心數據結構,控制反轉功能都是圍繞對這個BeanDefinition的處理來完成的,這些BeanDefinition就像是容器里裝的水一樣,有了這些基本數據,容器才能夠發揮作用。簡單來說,BeanDefinition在Spring中是用來描述Bean對象的,它本身並不是一個Bean實例,而是包含了Bean實例的所有信息,比如類名、屬性值、構造器參數、scope、依賴的bean、是否是單例類、是否是懶加載以及其它信息。其實就是將Bean實例定義的信息存儲到這個BeanDefinition相應的屬性中,后面Bean對象的創建是根據BeanDefinition中描述的信息來創建的,例如拿到這個BeanDefinition后,可以根據里面的類名、構造函數、構造函數參數,使用反射進行對象創建。也就是說 IOC容器可以有多個BeanDefinition,並且一個BeanDefinition對象對應一個<bean>標簽中的信息。

當然BeanDefinition的最終目的不只是用來存儲Bean實例的所有信息,而是為了可以方便的進行修改屬性值和其他元信息,比如通過BeanFactoryPostProcessor進行修改一些信息,然后在創建Bean對象的時候就可以結合原始信息和修改后的信息創建對象了。

我們先來看一下使用XmlBeanFactory的方式創建容器,即使XmlBeanFactory已經過時了,但是有必要還是說一說。(以上一章橙汁和添加劑的栗子來舉例)

//創建XmlBeanFactory對象,並且傳入Resource
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
//調用getBean方法獲取實例對象
OrangeJuice orangeJuice = (OrangeJuice) xmlBeanFactory.getBean("orangeJuice");
orangeJuice.needOrangeJuice();

可以發現這里的XmlBeanFactory構造函數中的參數是ClassPathResource類,而ClassPathResource類實現了Resource接口,這個Resource接口是定義資源文件的位置。在Spring框架中,如果我們需要讀取Xml文件的信息,我們就需要知道這個文件在哪,也就是指定這個文件的來源。要讓Spring知道這個來源,我們需要使用Resource類來完成。Resource類是Spring用來封裝IO操作的類,通過Resoruce類實例化出一個具體的對象,比如ClasspathResource構造參數傳入Xml文件名,然后將實例化好的Resource傳給BeanFactory的構造參數來加載配置、管理對象,這樣Spring就可以方便地定位到需要的BeanDefinition信息來對Bean完成容器的初始化和依賴注入過程,也就是說Spring的配置文件的加載少不了Resource這個類。在XmlBeanFactory中對Xml定義文件的解析通過委托給 XmlBeanDefinitionReader 來完成,我們可以在XmlBeanFactory中看到。

上面說了XmlBeanFactory已經淘汰不用了,那現在肯定有更好的方式來處理,我們先來分析一下XmlBeanFactory源碼:

@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    
    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }   
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }
}

通過XmlBeanFactory的源碼我們可以發現,在 XmlBeanFactory 中,初始化了一個 XmlBeanDefinitionReader對象,它的功能是讀取Xml文件,將Bean的xml配置文件轉換為多個BeanDefinition對象的工具類,一個BeanDefinition對象對應一個<bean>標簽中的信息。XmlBeanFactory 中額外還定義了兩個構造函數,可以看到第一個構造函數調用了第二個,所以重點看第二個,首先是調用了父類構造函數,然后執行loadBeanDefinition()方法,這個方法就是具體加載了BeanDefinition的操作,我們可以將這段代碼抽取出來。所以下面我們我們以編程的方式使用DefaultListableBeanFactory,從中我們可以看到IOC容器使用的一些基本過程,對我們了解IOC容器的工作原理是非常有幫助的,因為這個編程式使用IOC容器過程,很清楚的揭示了在IOC容器實現中那些關鍵的類,可以看到他們是如何把IOC容器功能解耦的,又是如何結合在一起為IOC容器服務的,DefaultListableBeanFactory方式創建容器如下:

//創建ClassPathResource對象,BeanDefinition的定義信息
ClassPathResource resource = new ClassPathResource("applicationContext.xml");

//創建一個DefaultListableBeanFactory對象,XmlBeanFactory 繼承了這個類
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

/*創建一個載入IOC容器配置文件的讀取器,這里使用XMLBeanFactory中使用的XmlBeanDefinitionReader讀取器
來載入XML文件形式的BeanDefinition,通過一個回到配置給BeanFactory*/
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);

/*從定義好的資源位置讀入配置信息,具體的解析過程有XmlBeanDefinitionReader來完成,
完成整個載入和注冊Bean定義后需要的IOC容器就建立起來了,這個時候就可以直接使用IOC容器了*/
reader.loadBeanDefinitions(resource);

//獲取實例對象並調用方法
OrangeJuice orangeJuice = (OrangeJuice) factory.getBean("orangeJuice");
orangeJuice.needOrangeJuice();

/*applicationContext.xml部分配置
<bean id="additive" class="com.thr.Additive"></bean>

<bean id="orangeJuice" class="com.thr.OrangeJuice">
<property name="additive" ref="additive"></property>
</bean>
*/

總結:這樣我們就可以通過Factory獨享來使用DefaultListableBeanFactory這個IOC容器了,在使用IOC容器時 需要以下幾個步驟:

  1. 創建IOC配置文件的Resource抽象資源,這個抽象資源包含了BeanDefinition的定義信息。
  2. 創建一個BeanFactory,這里使用DefaultListableBeanFactory。
  3. 創建一個載入BeanDefinition的讀取器,這里使用XmlBeanDefinitionReader來載入XML文件形式的BeanDefinition,通過一個回調配置給BeanFactory。
  4. 從定義好的資源位置讀取配置信息,具體的解析過程由 XmlBeanDefinitionReader來完成,完成整個載入和注冊Bean定義后,需要的IOC容器就建立起來了,這個時候就可以使用IOC容器了。

關於DefaultListableBeanFactory方式創建容器更加詳細的介紹可以參考:https://blog.csdn.net/csj941227/article/details/85050632

4、BeanFactory的詳細介紹

BeanFactory 接口位於 IOC容器設計的最底層,它提供了 Spring IOC容器最基本的功能,給具體的IOC容器的實現提供了規范。為此,我們來看看該接口中到底提供了哪些功能和規范(也就是接口中的方法),BeanFactory 接口中的方法如下圖所示:

image

可以看到這里定義的只是一系列的接口方法,通過這一系列的BeanFactory接口,可以使用不同的Bean的檢索方法,很方便的從IOC容器中得到需要的Bean,從而忽略具體的IOC容器的實現,從這個角度看的話,這些檢索方法代表的是最為基本的容器入口。其具體的方法有:5個獲取實例的方法(getBean的重載方法);2個獲取Bean的提供者;4個判斷的方法(判斷是否存在,是否為單例、原型,名稱類型是否匹配);2個獲取類型的方法和1個獲取別名的方法。

下面我們來看BeanFactory 具體的介紹:

public interface BeanFactory {

    //用戶使用容器時,可以使用轉義符“&”來得到FactoryBean本身
    String FACTORY_BEAN_PREFIX = "&";

    //獲取Bean
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    //獲取bean的提供者(對象工廠)
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    //判斷是否包含指定名字的bean
    boolean containsBean(String name); 
    //獲取指定名字的Bean是否是Singleton類型的Bean,對於Singleton屬性,用戶可以在BeanDefinition中指定
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    //獲取指定名字的Bean是否是Prototype類型的,與Singleton屬性一樣也可以在BeanDefinition中指定
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    //指定名字的bean是否和指定的類型匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch);
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    //獲取指定名字的Bean的Class類型
    Class<?> getType(String name) throws NoSuchBeanDefinitionException; 

    //獲取指定名字的Bean的所有別名,這些別名是用戶在BeanDefinition中定義的
    String[] getAliases(String name); 
}

正是由於BeanFactory是 Spring IoC 最底層的設計,其所有關於 Spring IoC 的容器將會遵守它所定義的方法。所以其內部定義的方法也極其重要,我們只有先搞清楚這個接口中的每一個方法,才能更好的理解IOC容器,下面我們對BeanFactory接口中的方法方法進行介紹。(同樣以前面橙汁和添加劑的栗子來舉例)如下:

(1)、常量部分FACTORY_BEAN_PREFIX = "&"

它的作用是如果在使用beanName獲取Bean時,在BeanName前添加這個前綴(”&BeanName”), 那么使用這個BeanName獲得的Bean實例是其所在FactoryBean的實例,也就是實現 FactoryBean 接口的那個類的Bean實例。

關於BeanFactory和FactoryBean的區別可以參考:https://blog.csdn.net/wangbiao007/article/details/53183764


(2)、getBean部分(重要):該方法表示獲取bean實例

①、根據名字獲取bean:getBean(String name)

 Object obj = (obj)factory.getBean("beanName");

注意:這種方法不太安全,IDE 不會檢查其安全性(關聯性),所以我們必須強制轉換類型。

②、根據類型獲取bean:getBean(Class<T> requiredType)

Object obj = factory.getBean(Bean.class);

注意:要求在 Spring 中只配置了一個這種類型的實例,否則報錯。(如果有多個那 Spring 就懵了,不知道該獲取哪一個)

③、根據名字和類型獲取bean(推薦):getBean(String name, Class<T> requiredType)

Object obj = factory.getBean("beanName",Bean.class);

這種方式解決上面兩個方法的問題,所以推薦使用這個方法。

④、根據名稱、類型和給定的構造函數參數或者工廠方法參數構造對象獲取bean

使用Bean名稱尋找對應的Bean,使用給定的構造函數參數或者工廠方法參數構造對象並返回,會重寫Bean定義中的默認參數。

Object getBean(String name, Object... args) throws BeansException

使用Bean類型尋找屬於該類型的Bean,用給定的構造函數參數或工廠方法參數構造對象並返回,會重寫Bean定義中的默認參數。

<T> T getBean(Class<T> requiredType, Object... args) throws BeansException

注意:該兩個方法只適用於prototype的Bean,默認作用域的Bean不能重寫其參數。


(3)、getBeanProvider部分:該方法表示獲取bean的提供者(對象工廠)

getBeanProvider方法用於獲取指定bean的提供者,可以看到它返回的是一個ObjectProvider,其父級接口是ObjectFactory。首先來看一下ObjectFactory,它是一個對象的實例工廠,只有一個方法:

T getObject() throws BeansException;

調用這個方法返回的是一個對象的實例。此接口通常用於封裝一個泛型工廠,在每次調用的時候返回一些目標對象新的實例。ObjectFactory和FactoryBean是類似的,只不過FactoryBean通常被定義為BeanFactory中的服務提供者(SPI)實例,而ObjectFactory通常是以API的形式提供給其他的bean。簡單的來說,ObjectFactory一般是提供給開發者使用的,FactoryBean一般是提供給BeanFactory使用的。

ObjectProvider繼承ObjectFactory,特為注入點而設計,允許可選擇性的編程和寬泛的非唯一性的處理。在Spring 5.1的時候,該接口從Iterable擴展,提供了對Stream的支持。該接口的方法如下:

// 獲取對象的實例,允許根據顯式的指定構造器的參數去構造對象
T getObject(Object... args) throws BeansException;
// 獲取對象的實例,如果不可用,則返回null
T getIfAvailable() throws BeansException;
T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException;
void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException;
// 獲取對象的實例,如果不是唯一的或者沒有首先的bean,則返回null
T getIfUnique() throws BeansException;
T getIfUnique(Supplier<T> defaultSupplier) throws BeansException;
void ifUnique(Consumer<T> dependencyConsumer) throws BeansException;

// 獲取多個對象的實例
Iterator<T> iterator();
Stream<T> stream();
Stream<T> orderedStream()

這些接口是分為兩類,

  • 一類是獲取單個對象,getIfAvailable()方法用於獲取可用的bean(沒有則返回null),getIfUnique()方法用於獲取唯一的bean(如果bean不是唯一的或者沒有首選的bean返回null)。getIfAvailable(Supplier<T> defaultSupplier)getIfUnique(Supplier<T> defaultSupplier),如果沒有獲取到bean,則返回defaultSupplier提供的默認值,ifAvailable(Consumer<T> dependencyConsumer)ifUnique(Consumer<T> dependencyConsumer)提供了以函數式編程的方式去消費獲取到的bean。
  • 另一類是獲取多個對象,stream()方法返回連續的Stream,不保證bean的順序(通常是bean的注冊順序)。orderedStream()方法返回連續的Stream,預先會根據工廠的公共排序比較器進行排序,一般是根據org.springframework.core.Ordered的約定進行排序。

(4)、其它部分是一些工具性的方法

  • containsBean(String name):通過名字判斷是否包含指定bean的定義 。
  • isSingleton(String name) isPrototype(String name):判斷是單例和原型(多例)的方法。(注意:在默認情況下,isSingleton為 ture,而isPrototype為 false )。如果isSingleton為true,其意思是該 Bean 在容器中是作為一個唯一單例存在的。而isPrototype則相反,如果判斷為真,意思是當你從容器中獲取 Bean,容器就為你生成一個新的實例。
  • isTypeMatch:判斷給定bean的名字是否和類型匹配 。
  • getType(String name):根據bean的名字來獲取其類型的方法 (按 Java 類型匹配的方式 )。
  • getAliases(String name):根據bean的名字來獲取其別名的方法。

(5)、ResolvableType參數介紹

或許你已經注意到了,有兩個方法含有類型是ResolvableType的參數,那么ResolvableType是什么呢?假如說你要獲取泛型類型的bean:MyBean ,根據Class來獲取,肯定是滿足不了要求的,泛型在編譯時會被擦除。使用ResolvableType就能滿足此需求,代碼如下:

ResolvableType type = ResolvableType.forClassWithGenerics(MyType.class, TheType.class);
ObjectProvider<MyType<TheType>> op = applicationContext.getBeanProvider(type);
MyType<TheType> bean = op.getIfAvailable()

簡單的來說,ResolvableType是對Java java.lang.reflect.Type的封裝,並且提供了一些訪問該類型的其他信息的方法(例如父類, 泛型參數,該類)。從成員變量、方法參數、方法返回類型、類來構建ResolvableType的實例。

5、ApplicationContext容器的設計原理

我們知道ApplicationContext容器是擴展BeanFactory容器而來,在BeanFactory的基本讓IoC容器功能更加豐富。如果說BeanFactory是Sping的心臟(提供了IOC容器的基本功能),那么ApplicationContext就是完整的身軀了(提供了更加高級的功能)。所以我們來看一下ApplicationContext和它的基礎實現類的體系結構圖,如下所示:

image

卧槽、卧槽(奈何自己沒文化,出口只能卧槽imageimageimage),還是關了吧,這也太復雜了,看到這么復雜是不是就不想看了?別急,我們暫時只看最下面一排即可。可以看到ClassPathXmlApplicationContext這個類我們比較熟悉,因為在第二章Spring的入門案例中我們已經使用過ClassPathXmlApplicationContext這個類了。所以在ApplicationContext容器中,我們以常用的ClassPathXmlApplicationContext的實現為例來說明ApplicationContext容器的設計原理。使用classpath路徑下的xml配置文件加載bean的方式如下:

ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");

下面對此代碼進行分析,追蹤源碼來介紹它的設計原理如下所示:

首先是new了ClassPathXmlApplicationContext對象,並且構造參數傳入了一個xml文件,我們進入其構造方法(核心)如下:

    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

上面的參數configLocation表示的是Spring配置文件的路徑,可以發現后面又調用了內部另一個構造方法如下:

    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {
        // 1.初始化父類
        super(parent);
        // 2.設置本地的配置信息
        setConfigLocations(configLocations);
        // 3.完成Spring IOC容器的初始化
        if (refresh) {
            refresh();
        }
    }

首先初始化了父類,就是一直到父類AbstractApplicationContext中,將ApplicationContext的環境屬性設置給本類的環境屬性,包括一些profile,系統屬性等。

然后設置本地的配置文件信息,這里調用其父類AbstractRefreshableConfigApplicationContext 的 setConfigLocations 方法,該方法主要處理ClassPathXmlApplicationContext傳入的字符串中的占位符,即解析給定的路徑數組(這里就一個),setConfigLocations 方法源碼如下:

    public void setConfigLocations(@Nullable String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                //循環取出每一個path參數,在此處就一個applicationContext.xml
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

setConfigLocations方法除了處理ClassPathXmlApplicationContext傳入的字符串中的占位符之外,其實還有一個作用:創建環境對象ConfigurableEnvironment。詳細可以參考:https://blog.csdn.net/boling_cavalry/article/details/80958832

當本地配置文件解析完成之后,就可以准備實現容器的各個功能了。

然后調用了refresh()方法,這個方法非常非常非常重要,它算是ApplicationContext容器最核心的部分了,因為這個refresh過程會牽涉IOC容器啟動的一系列復雜操作,ApplicationContext的refresh()方法里面操作的不只是簡單 IoC容器,而是高級容器的所有功能(包括 IoC),所以你說這個方法重不重要。而對於不同的高級容器的實現,其操作都是類似的(比如FileSystemXmlApplicationContext),因此將其具體的操作封裝在父類 AbstractApplicationContext 中,在其子類中僅僅涉及到簡單的調用而已。所以我們來看看AbstractApplicationContext類,可以看到refresh方法的源碼如下(AbstractApplicationContext.refresh() 源碼脈絡):

//AbstractApplicationContext.refresh()方法
public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            //刷新上下文環境
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            //這里是在子類中啟動 refreshBeanFactory() 的地方,獲得新的BeanFactory,解析XML、Java類,並加載BeanDefinition
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            //准備bean工廠,以便在此上下文中使用
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                //設置 beanFactory 的后置處理
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //調用 BeanFactory 的后處理器,這些處理器是在Bean 定義中向容器注冊的
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                //注冊Bean的后處理器,在Bean創建過程中調用
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                //對上下文中的消息源進行初始化
                initMessageSource();

                // Initialize event multicaster for this context.
                //初始化上下文中的事件機制
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                //初始化其他特殊的Bean
                onRefresh();

                // Check for listener beans and register them.
                //檢查監聽Bean並且將這些監聽Bean向容器注冊
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                //實例化所有的(non-lazy-init)單件
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                //發布容器事件,結束Refresh過程
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                //重置Spring公共的緩存
                resetCommonCaches();
            }
        }
    }

對上面refresh方法中調用的各個方法詳細的介紹:

  • prepareRefresh() :為刷新准備上下文,主要設置狀態量(是否關閉,是否激活),記錄啟動時間,初始化屬性資源占位符、校驗必填屬性是否配置及初始化用於存儲早期應用事件的容器。
  • obtainFreshBeanFactory():主要用於獲取一個新的BeanFactory,如果BeanFactory已存在,則將其銷毀並重建,默認重建的BeanFactory為AbstractRefreshableApplicationContext;此外此方法委托其子類從XML中或基於注解的類中加載BeanDefinition。
  • prepareBeanFactory():配置BeanFactory使其具有一個上下文的標准特征,如上下文的類加載器、后處理程序(post-processors,如設置如總感知接口)。
  • postprocessBeanFactory():在應用上下文內部的BeanFactory初始化結束后對其進行修改,在所有的BeanDefinition已被加載但還沒有實例化bean, 此刻可以注冊一些特殊的BeanPostFactory,如web應用會注冊ServletContextAwareProcessor等。
  • invokeBeanFactoryPostProcessors():調用注冊在上下文中的BeanFactoryPostProcessor,如果有順序則按順序調用,並且一定再單列對象實例化之前調用。
  • registerBeanPostProcessors():實例化並注冊BeanPostProcessor,如果有顯式的順序則按照順序調用一定在所有bean實例化之前調用。
  • initMessageSource():初始化MessageSource,如果當前上下文沒有定義則使用其父類的,如果BeanFactory中不能找到名稱為messageSource中的bean, 則默認使用DelegatingMessageSource。
  • initApplicationEventMulticaster():初始化ApplicationEventMulticaster,如果上下文沒有定義則默認使用SimpleApplicationEventMulticaster,此類主要用於廣播ApplicationEvent。
  • onRefresh() :在一些特定的上下文子類中初始化特定的bean,如在Webapp的上下文中初始化主題資源。
  • registerListeners():添加實現了ApplicationListener的bean作為監聽器,它不影響非bean的監聽器;還會使用多播器發布早期的ApplicationEvent。
  • finishBeanFactoryInitialization():實例化所有非延遲加載的單例,完成BeanFactory的初始化工作。
  • finishRefresh():完成上下文的刷新工作,調用LifecycleProcessor的onFresh()及發布的ContextRefreshEvent事件。
  • resetCommonCaches():重置Spring公共的緩存,如:ReflectionUtils、ResolvableType、CachedIntrospectionResults的緩存CachedIntrospectionResults的緩存。

上述各個方法的詳細介紹可以參考:https://blog.csdn.net/boling_cavalry/article/details/81045637

ApplicationContext的設計原理暫時就介紹到這里吧!!!下面來介紹一下ApplicationContext容器中常用的一些實現類。

6、ApplicationContext的詳細介紹

對於ApplicationContext高級容器的詳細介紹我們就不看它的的源碼了,主要來介紹一下它的具體實現類,因為平時我們在開發中使用它的實現類比較多。ApplicationContext的中文意思為“應用上下文”,它繼承自BeanFactory,給IOC容器提供更加高級的功能,所以我們稱它為高級容器,ApplicationContext接口有以下常用的實現類,如下所示:

實現類 描述
ClassPathXmlApplicationContext 從系統類路徑classpath下加載一個或多個xml配置文件,適用於xml配置的方式
FileSystemXmlApplicationContext 從系統磁盤下加載一個或多個xml配置文件(必須有訪問權限)
XmlWebApplicationContext 從web應用下加載一個或多個xml配置文件,適用於web應用的xml配置方式
AnnotationConfigApplicationContext 從Java注解的配置類中Spring的ApplicationContext容器。使用注解避免使用application.xml進行配置。相比XML配置,更加便捷
AnnotationConfigWebApplicationContext 專門為web應用准備的用於讀取注解創建容器的類

下面詳細介紹各個實現類的使用方式:

(1)、ClassPathXmlApplicationContext:從系統類路徑classpath下加載一個或多個xml配置文件,找到並裝載完成ApplicationContext的實例化工作。例如:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

(2)、FileSystemXmlApplicationContext:從系統磁盤下加載一個或多個xml配置文件(必須有訪問權限)。也就是讀取系統磁盤指定路徑的xml文件。例如:

ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");

它與ClassPathXmlApplicationContext的區別在於讀取Spring配置文件的方式,FileSystemXmlApplicationContext不在從類路徑下讀取配置文件,而是通過制定參數從系統磁盤讀取,前提是有訪問權限。


(3)、XmlWebApplicationContext:從web應用下加載一個或多個xml配置文件,適用於web應用的xml配置方式。

在Java項目中提供ClassPathXmlApplicationContext類手工實例化ApplicationContext容器通常是不二之選,但是對於Web項目就不行了,Web項目的啟動是由相應的Web服務器負責的,因此,在Web項目中ApplicationContext容器的實例化工作最好交由Web服務器來完成。Spring為此提供了以下兩種方式:

  • org.springframework.web.context.ContextLoaderListener
  • org.springframework.web.context.ContexLoaderServlet(此方法目前以廢棄)

ContextLoaderListener方式只適用於Servlet2.4及以上規范的Servlet,並且需要Web環境。我們需要在web.xml中添加如下配置:

    <!--從類路徑下加載Spring配置文件,classpath特指類路徑下加載-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
        </param-value>
    </context-param>
    <!--以Listener的方式啟動spring容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

當Spring容器啟動后就可以在項目中獲取對應的實例了。例如:

@WebServlet("/MyServlet")
public class MyServlet {

        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //創建XmlWebApplicationContext對象,但這時並沒有初始化容器
            XmlWebApplicationContext context = new XmlWebApplicationContext();
            // 指定配置文件路徑
            context.setConfigLocation("application.xml");
            // 需要指定ServletContext對象
            context.setServletContext(request.getServletContext());
            // 初始化容器
            context.refresh();
            //獲取實例
            Additive additive = (Additive) context.getBean("additive");
            additive.addAdditive();

        }
}

(4)、AnnotationConfigApplicationContext:從Java注解的配置類中加載Spring的ApplicationContext容器。使用注解避免使用application.xml進行配置。相比XML配置,更加便捷。

創建一個AppConfig配置類(OrangeJuice和Additive類參考上一章內容)。例如:

@Configuration
public class AppConfig {

    @Bean(name = "orangeJuice")
    public OrangeJuice orangeJuice(){
        OrangeJuice orangeJuice = new OrangeJuice();
        return orangeJuice;
    }

    @Bean(name = "additive")
    public Additive additive(){
        Additive additive = new Additive();
        return additive;
    }
}

注意:@Configuration和@Bean注解的介紹和理解

  • @Configuration可理解為用spring的時候xml里面的 標簽。
  • @Bean可理解為用spring的時候xml里面的 標簽,默認name為方法名。

使用AnnotationConfigApplicationContext獲取Spring容器實例。代碼如下:

   //創建AnnotationConfigApplicationContext對象,此時並沒有初始化容器
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   //將AppConfig中的配置注冊至容器中
   context.register(AppConfig.class);
   // 初始化容器
   context.refresh();

   //獲取實例對象
   OrangeJuice orangeJuice = (OrangeJuice) context.getBean("orangeJuice");
   Additive additive = (Additive) context.getBean("additive");
   orangeJuice.setAdditive(additive);
   orangeJuice.needOrangeJuice();

(5)、AnnotationConfigWebApplicationContext:專門為web應用准備的用於讀取注解創建容器的類。

如果是Web項目使用@Configuration的java類提供配置信息的配置 web.xml 配置修改如下:

    <!--通過指定context參數,讓Spring使用AnnotationConfigWebApplicationContext啟動容器
    而非XmlWebApplicationContext。默認沒配置時是使用XmlWebApplicationContext-->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>
    <!--指定標注了@Configuration的類,多個可以用逗號分隔-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.thr.AppConfig</param-value>
    </context-param>
    <!--監聽器將根據上面的配置使用AnnotationConfigWebApplicationContext
    根據contextConfigLocation指定的配置類啟動Spring容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

7、ApplicationContext容器擴展功能詳解介紹

前面在介紹BeanFactory和ApplicationContext的區別是生成了一張圖如下:

image

我們知道ApplicationContext容器正是因為繼承了紅框中的這些接口,使用才讓ApplicationContext容器有了更加高級的功能。所以下面來詳細介紹紅框中各個接口:

(1)、ListableBeanFactory——可將Bean逐一列出的工廠

ListableBeanFactory接口能夠列出工廠中所有的bean,下面是該接口的源碼:

/**
 * ListableBeanFactory源碼介紹
 */
public interface ListableBeanFactory extends BeanFactory {
    //判斷是否包含給定名字的bean的定義
    boolean containsBeanDefinition(String beanName);
    //獲取工廠中bean的定義的數量
    int getBeanDefinitionCount();
    //獲取工廠中所有定義了的bean的名字(包括子類)
    String[] getBeanDefinitionNames();

    //獲取指定類型的bean的名字(includeNonSingletons為false表示只取單例Bean,true則不是;
    //allowEagerInit為true表示立刻加載,false表示延遲加載。 注意:FactoryBeans都是立刻加載的。)
    String[] getBeanNamesForType(ResolvableType type);
    String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit);
    String[] getBeanNamesForType(@Nullable Class<?> type);
    String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);

    //根據指定的類型來獲取所有的bean名和bean對象的Map集合(包括子類)
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
            throws BeansException;

    //根據注解類型,獲取所有有這個注解的bean名稱
    String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
    //根據注解類型,獲取所有有這個注解的bean名和bean對象的Map集合
    Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
    //根據bean名和注解類型查找所有指定的注解(會考慮接口和父類中的注解)
    @Nullable
    <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
            throws NoSuchBeanDefinitionException;
}

上面的這些方法都不考慮祖先工廠中的bean,只會考慮在當前工廠中定義的bean。


(2)、HierarchicalBeanFactory——分層的Bean工廠

HierarchicalBeanFactory接口定義了BeanFactory之間的分層結構,ConfigurableBeanFactory中的setParentBeanFactory方法能設置父級的BeanFactory,下面列出了HierarchicalBeanFactory中定義的方法:

/**
 * HierarchicalBeanFactory源碼介紹
 */
public interface HierarchicalBeanFactory extends BeanFactory {

    //獲取本Bean工廠的父工廠
    @Nullable
    BeanFactory getParentBeanFactory();

    //本地的工廠是否包含指定名字的bean
    boolean containsLocalBean(String name);
}

這兩個方法都比較直接明了,getParentBeanFactory方法用於獲取父級BeanFactory。containsLocalBean用於判斷本地的工廠是否包含指定的bean,忽略在祖先工廠中定義的bean。


(3)、MessageSource——消息的國際化

在前面也提到過MessageSource,它主要用於消息的國際化,下面是該接口的源碼:

// 獲取消息
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

以上的三個方法都是用於獲取消息的,第一個方法提供了默認消息,第二個接口如果沒有獲取到指定的消息會拋出異常。第三個接口中的MessageSourceResolvable參數是對代碼、參數值、默認值的一個封裝。


(4)、ApplicationEventPublisher

ApplicationEventPublisher接口封裝了事件發布功能,提供Spring中事件的機制。接口中的方法定義如下:

// 發布事件
void publishEvent(ApplicationEvent event);
void publishEvent(Object event);

第一個方法用於發布特定於應用程序事件。第二個方法能發布任意的事件,如果事件不是ApplicationEvent,那么會被包裹成PayloadApplicationEvent事件。


(5)、EnvironmentCapable

EnvironmentCapable提供了訪問Environment的能力,該接口只有一個方法:

Environment getEnvironment();

Environment表示當前正在運行的應用的環境變量,它分為兩個部分:profiles和properties。它的父級接口PropertyResolver提供了property的訪問能力。


(6)、ResourceLoader和ResourcePatternResolver

首先來看一下ResourceLoader,聽名字就知道該接口是用來加載資源的策略接口(例如類路徑或者文件系統中的資源)。該接口中的源碼如下:

/**
 * ResourceLoader源碼介紹
 */
public interface ResourceLoader {

    //用於從類路徑加載的偽URL前綴:" classpath:"。
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    //根據指定的位置獲取資源
    Resource getResource(String location);
    //獲取該資源加載器所使用的類加載器
    ClassLoader getClassLoader();
}

該接口只有簡單明了的兩個方法,一個是用來獲取指定位置的資源,一個用於獲取資源加載器所使用的類加載器。

Resource是從實際類型的底層資源(例如文件、類路徑資源)進行抽象的資源描述符。再看下Resource的源碼:

/**
 * Resource源碼介紹
 */
public interface Resource extends InputStreamSource {

    boolean exists(); // 資源實際上是否存在

    boolean isReadable(); // 資源是否可讀

    boolean isOpen(); // 檢查資源是否為打開的流

    boolean isFile(); // 資源是否為文件系統上的一個文件

    URL getURL() throws IOException; // 獲取url

    URI getURI() throws IOException; // 獲取URI

    File getFile() throws IOException; // 獲取文件

    ReadableByteChannel readableChannel() throws IOException; // 獲取ReadableByteChannel

    long contentLength() throws IOException; // 資源的內容的長度

    long lastModified() throws IOException; // 資源的最后修改時間

    // 相對於當前的資源創建一個新的資源
    Resource createRelative(String relativePath) throws IOException;

    String getFilename(); // 獲取資源的文件名

    String getDescription(); // 獲取資源的描述信息
}

Resource的父級接口為InputStreamSource,可以簡單的理解為InputStream的來源,其內部只有一個方法,如下:

// 獲取輸入流
InputStream getInputStream() throws IOException; 

接下來在來看一下ResourcePatternResolver,該接口用於解析一個位置模式(例如Ant風格的路徑模式),該接口也只有一個方法,如下:

// 將給定的位置模式解析成資源對象
Resource[] getResources(String locationPattern) throws IOException;

至此BeanFactory和ApplicationContext容器的設計已經全部介紹完了。如果哪里有問題歡迎大家多多討論,留言,畢竟LZ(樓主)還是一個菜鳥,我也正在每天積累,學習,慢慢走向禿頂的路上。


參考資料:

  1. 《JavaEE入門實戰》
  2. https://www.cnblogs.com/zhangfengxian/p/11086695.html
  3. https://www.cnblogs.com/ChenD/p/10235579.html
  4. https://www.cnblogs.com/lichangyun/p/10698951.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM