Spring IOC容器的設計與實現

Halavah發表於2024-06-06

Spring IOC容器的設計與實現

1、Spring IOC 容器的設計

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

  • 實現 BeanFactory 介面的簡單容器
  • 實現 ApplicationContext 介面的高階容器

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

透過上面的圖片我們可以發現​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

臥槽、臥槽(奈何自己沒文化,出口只能臥槽),還是關了吧,這也太複雜了,看到這麼複雜是不是就不想看了?別急,我們暫時只看最下面一排即可。可以看到 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(樓主)還是一個菜鳥,我也正在每天積累,學習,慢慢走向禿頂的路上。

相關文章