在本講,我們來對Spring IoC功能相關的接口逐一進行分析,分析這些接口的原因就是為了我們自己定義Spring IoC功能提前做好準備。
Spring IoC相關接口分析
BeanFactory接口解析
對于BeanFactory接口,我之前只是稍微提到過,并且將就著用了一下它。這里,我將會對BeanFactory接口進行一個具體講解。
Spring中bean的創建是典型的工廠模式,這一系列的bean工廠,即IoC容器,為開發者管理對象之間的依賴關系提供了很多便利和基礎服務,在Spring中有許多IoC容器的實現可供用戶選擇,其相互關系如下圖所示。
這里說到了Spring中bean的創建是典型的工廠模式,那么你知道到底是哪種工廠模式嗎?其實,這里面用的是簡單工廠+配置文件的形式,相信大家都知道簡單工廠+配置文件會大大降低對象和對象之間的耦合。
上面還說到了在Spring中有許多IoC容器的實現可供用戶選擇,這句話怎么理解呢?我們在創建IoC容器時,創建的肯定是BeanFactory接口的子實現類對象,那么我們就要想了,到底有哪些子實現類可供咱選擇呢?嘿嘿!這不用你操心,因為Spring提供了很多該接口的子實現類供我們去選擇。
從以上類圖中可以看到,BeanFactory作為最頂層的一個接口,定義了IoC容器的基本功能規范,而且BeanFactory有三個重要的子接口,分別是ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是從類圖中我們可以發現最終的默認實現類是DefaultListableBeanFactory,它實現了所有的接口,這就意味著如果我們想要用的話,那么直接用該子實現類就行了!
看完上面這段話,有兩點需要引起我們的注意,一是BeanFactory作為最頂層的一個接口,定義了IoC容器的基本功能規范,那么到底它定義了哪些最基本的功能規范呢?其實,大家想一想就知道了,工廠本身就是用來生產對象的,那么在Spring里面,bean工廠生產的就是bean對象了,所以BeanFactory里面肯定是要提供獲取bean對象的方法的,這個我后面就會詳細地講到;二是BeanFactory屬于延時加載,也就是說對于bean對象Spring進行了一個延時加載。
問題來了,為何要定義這么多層次的接口呢?定義的少一點,整個架構看起來不就更加簡單嗎?原因如下:
每個接口都有它的使用場合,主要是為了區分在Spring內部操作過程中對象的傳遞和轉化,對對象的數據訪問所做的限制。例如,
- ListableBeanFactory接口:表示bean可列表化。什么意思啊?我給大家解釋解釋,它說的是該接口可以通過列表的方式對bean對象進行一個存儲。
- HierarchicalBeanFactory接口:表示bean是有繼承關系的,也就是每個bean可能有父bean。
- AutowireCapableBeanFactory接口:定義bean的自動裝配規則。依賴注入就屬于自動裝配規則里面的。
以上這三個接口共同定義了bean的集合、bean之間的關系及bean的行為。不過,最基本的IoC容器接口還是BeanFactory,下面我們就來看一下它的源碼,看它里面到底定義了哪些最基本的功能規范。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.beans.factory; import org.springframework.beans.BeansException; import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; // 根據bean的名稱獲取IoC容器中的bean對象 Object getBean(String var1) throws BeansException; // 根據bean的名稱獲取IoC容器中的bean對象,并指定獲取到的bean對象的類型,這樣我們使用時就不需要進行類型強轉了 <T> T getBean(String var1, Class<T> var2) throws BeansException; Object getBean(String var1, Object... var2) throws BeansException; <T> T getBean(Class<T> var1) throws BeansException; <T> T getBean(Class<T> var1, Object... var2) throws BeansException; <T> ObjectProvider<T> getBeanProvider(Class<T> var1); <T> ObjectProvider<T> getBeanProvider(ResolvableType var1); // 判斷容器中是否包含指定名稱的bean對象 boolean containsBean(String var1); // 根據bean的名稱判斷是否是單例 boolean isSingleton(String var1) throws NoSuchBeanDefinitionException; boolean isPrototype(String var1) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String var1) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException; String[] getAliases(String var1); }
看到那一系列的getBean方法沒,它們都是用來獲取bean對象的,當然了,BeanFactory接口里面還定義了一些其他的基本功能規范,這里我就不再細說了。
在BeanFactory里只對IoC容器的基本行為做了定義,根本不關心你的bean是如何定義及怎樣加載的。正如我們只關心能從工廠里得到什么產品,而不關心工廠是怎么生產這些產品的一樣。當然,bean的定義以及加載是要交由給BeanFactory接口的子實現類去做的。
BeanFactory有一個很重要的子接口,就是ApplicationContext接口,該接口主要是來規范容器中的bean對象是非延時加載的,即在創建容器對象的時候就對bean對象進行初始化,并存儲到一個容器中。大家不妨來看一下下面這張圖。
可以看到最頂層就是BeanFactory接口,它下面有一個子接口叫ApplicationContext,而該子接口下面又有三個比較重要的子實現類,還記得上面我說過在Spring中有許多IoC容器的實現可供用戶選擇嗎?這仨子實現類就是。如果要想知道工廠是如何產生對象的,那么我們就需要查看具體的IoC容器實現了,Spring提供了許多IoC容器實現,比如:
- FileSystemXmlApplicationContext:根據系統路徑加載XML配置文件,并創建IoC容器對象。
- ClassPathXmlApplicationContext:根據類路徑加載XML配置文件,并創建IoC容器對象。
- AnnotationConfigApplicationContext:加載注解類配置,并創建IoC容器。
注意了,我們在后面自己去定義Spring IoC功能時,我們只針對ClassPathXmlApplicationContext類來實現,也就是只關注類路徑下XML配置文件的解析與對應IoC容器的創建。
關于BeanFactory接口的分析,我們就分析至此。
BeanDefinition接口解析
Spring IoC容器管理的是我們定義的各種bean對象及其相互關系,而bean對象在Spring實現中是以BeanDefinition來描述的。
來看一下下面配置文件中的bean配置,如果你用過Spring或者Spring MVC框架的話,那么相信你對這段配置肯定不會陌生,注意了,在<bean>標簽內我們還可以設置很多屬性,例如scope、init-method、destory-method等,只是在這里我們并沒有全部列舉出來。
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl"></bean>
現在對于Spring來說的話,它就得解析這個<bean>標簽了,解析時,必然就要把該<bean>標簽對應的屬性的值進行一個封裝,那Spring會封裝成什么樣的一個對象呢?會封裝成BeanDefinition對象,又由于BeanDefinition是一個接口,所以最終Spring會封裝成一個該接口的子實現類對象。
接下來,我們就來看看BeanDefinition接口的繼承體系,如下圖所示。
可以看到,BeanDefinition確實是一個接口,而且它下面有一個具體的子實現類,即RootBeanDefinition。
BeanDefinitionReader接口解析
剛才我們講解完了BeanDefinition接口,知道了該接口的作用就是對XML配置文件里面<bean>標簽相關的屬性進行封裝。那么接下來我們就來思考一個問題,就是XML配置文件到底是由誰來解析的呢?既然提到解析了,那么我們就要來看一看BeanDefinitionReader接口了。
bean的解析過程非常復雜,功能被分得很細,因為這里需要被擴展的地方很多,必須保證足夠的靈活性,以應對可能的變化。bean的解析主要就是對Spring配置文件的解析,這個解析過程主要通過BeanDefinitionReader來完成。下面我們就來看看Spring中BeanDefinitionReader的類結構圖,如下圖所示。
當然了,你也可以回到IDEA里面去查看一下BeanDefinitionReader接口的繼承體系,如下圖所示。
可以看到,BeanDefinitionReader接口有三個子實現類,這里我只講一下上面紅框框住的兩個子實現類。
- PropertiesBeanDefinitionReader:主要解析properties格式的配置文件。但是,在實際開發中,你會發現很少會用到properties格式的配置文件,用的更多的是XML格式的配置文件。
- XmlBeanDefinitionReader:主要解析XML格式的配置文件。
BeanDefinitionReader既然是一個接口的話,那么它里面定義的便是最基本的功能規范,這些規范針對不同的子實現類會有不同的實現,從上圖中我們也看到了BeanDefinitionReader接口確實是有不同的子實現類。這些子實現類會來決定到底解析什么樣的配置文件,究竟是properties格式的呢,還是XML格式的,所以你會發現Spring底層設計的還是比較全面的。
接下來,我們就來看一下BeanDefinitionReader接口的源碼,看它里面到底定義了哪些最基本的功能規范。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.beans.factory.support; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.lang.Nullable; public interface BeanDefinitionReader { // 獲取BeanDefinitionRegistry注冊器對象 BeanDefinitionRegistry getRegistry(); @Nullable ResourceLoader getResourceLoader(); @Nullable ClassLoader getBeanClassLoader(); BeanNameGenerator getBeanNameGenerator(); /* * 下面這些重載的loadBeanDefinitions方法都是從指定的資源中加載bean定義 */ int loadBeanDefinitions(Resource var1) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... var1) throws BeanDefinitionStoreException; int loadBeanDefinitions(String var1) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... var1) throws BeanDefinitionStoreException; }
可以看到,BeanDefinitionReader接口里面定義了很多很多的方法,不過我們重點關注兩類方法:
- getRegistry方法:獲取BeanDefinitionRegistry注冊器對象。
- loadBeanDefinitions方法:從不同指定的資源中加載bean定義,也就是加載配置文件。
我相信,從BeanDefinitionReader接口定義的功能中你已經理解了它具體的一個作用。
BeanDefinitionRegistry接口解析
接下來,我們來分析一下BeanDefinitionRegistry接口。其實,剛才我們在去分析BeanDefinitionReader接口的時候就見過,還記得嗎?BeanDefinitionReader接口里面的getRegistry方法的返回值類型就是BeanDefinitionRegistry。
我們都知道,BeanDefinitionReader是用來解析bean定義,并將其(指的就是bean定義)封裝成BeanDefinition對象的。還有,我想大家也知道我們定義的配置文件中會定義很多bean標簽,那么這里就存在一個問題了,就是解析出來的BeanDefinition對象到底存儲到哪兒了呢?答案就是BeanDefinition的注冊中心,而該注冊中心頂層接口就是BeanDefinitionRegistry。
接下來,我們就來看一下BeanDefinitionRegistry接口的源碼,看它里面到底定義了哪些最基本的功能規范。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.beans.factory.support; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.AliasRegistry; public interface BeanDefinitionRegistry extends AliasRegistry { // 往注冊表中注冊bean void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException; // 從注冊表中刪除指定名稱的bean void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException; // 獲取注冊表中指定名稱的bean BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException; // 判斷注冊表中是否已經注冊了指定名稱的bean boolean containsBeanDefinition(String var1); // 獲取注冊表中所有的bean的名稱 String[] getBeanDefinitionNames(); int getBeanDefinitionCount(); boolean isBeanNameInUse(String var1); }
以上就是注冊中心頂層接口BeanDefinitionRegistry里面定義的最基本的功能規范。
由于BeanDefinitionRegistry是一個接口,所以我們在使用的時候肯定就是使用它的子實現類了。接下來,我們就來看一下Spring中BeanDefinitionRegistry的類結構圖,如下圖所示。
當然了,你也可以回到IDEA里面去查看一下BeanDefinitionRegistry接口的繼承體系,如下圖所示。
從上面可以看到BeanDefinitionRegistry接口的子實現類主要有以下幾個:
- SimpleBeanDefinitionRegistry
從名字上來看,它就是一個簡單的BeanDefinition的注冊中心,由于解析出來的BeanDefinition對象就存儲在BeanDefinition的注冊中心,所以它必然是一個容器。在這里我要給大家提個醒,相比另外兩個類,該類是我們要更加要關注的。
接下來,我們就要看看該類里面有沒有定義什么容器來存儲BeanDefinition對象了。查看該類的源碼,如下圖所示,你會發現在其成員位置處定義了一個Map集合,而且Map集合的鍵是String類型的,值是BeanDefinition類型的。其實,從這里就可以看出,該Map集合就是用來注冊BeanDefinition對象的,其中,鍵就是要注冊的BeanDefinition對象的名稱,值就是要注冊的BeanDefinition對象,不知我這樣說,大家明白了沒?
- DefaultListableBeanFactory
該類我們在分析BeanFactory接口的時候就見過,還記得嗎?不記得的話,再回頭去看一下BeanFactory的類結構圖。你會發現該類不僅實現了BeanFactory接口,還實現了BeanDefinitionRegistry接口,所以該類既是容器,也是注冊表。
接下來,我們也是要看看該類里面有沒有定義什么容器來存儲BeanDefinition對象。查看該類的源碼,如下圖所示,你會發現在其成員位置處也定義了一個Map集合來注冊BeanDefinition對象。
- GenericApplicationContext
該類間接地實現了ApplicationContext接口,這點你通過查閱源碼就能知道了,所以該類同上,既是容器,也是注冊表。
創建容器
剛才我們分析了一下與Spring IoC功能相關的一些接口,分析完這些接口之后,大家要明確的就是每一個接口,它的作用是什么,以及該接口下面比較常用的子實現類有哪些。
明確了之后,接下來我們再來分析一個問題,就是創建容器的時候,到底做了些什么事?
我們都知道BeanFactory是Spring IoC容器最頂層的一個接口,但咱們現在寫的程序用的卻是ApplicationContext這個子接口及其下面的ClassPathXmlApplicationContext子實現類,這是為什么呢?我不說,想必大家也知道,無非就是ApplicationContext屬于非延時加載,也就是說在創建容器對象的時候,就會去實例化bean對象,并存儲在容器里面了。
下面我們就以ClassPathXmlApplicationContext這個容器類來分析一下創建容器的時候,到底都做了些什么事。
首先,查看一下ClassPathXmlApplicationContext類的源碼,如下圖所示,可以看到它里面提供了很多構造方法,有無參的,有有參的,反正是有很多,三歲小孩都知道當我們去創建這個類的對象時,必然是要調用它里面的構造方法的。
然后,我們就來看一下咱們平時調用的有參構造到底做了哪些事情。你會發現該有參構造又調用了另外一個有參構造,那這個有參構造又是誰呢?看,是它!
可以看到,在這個構造方法里面會先判斷refresh變量是否為true,若為true則調用refresh方法。很顯然,該refresh變量的值就是true,因為從上一個有參構造跳轉到該有參構造時,第二個參數傳遞的就是true。既然refresh變量的值為true,那么肯定就會去調用refresh方法。
那么,refresh方法又做了些什么呢?點擊進入該方法去看看不就得了,你會發現此時跳轉到父類中了,如下圖所示。
可以看到,該refresh方法做了很多很多事情,這里我就做一個簡短說明,refresh方法做的事就是加載配置文件并去初始化bean對象,然后將bean對象存儲在容器里面。注意,該方法的具體源代碼,我們就不逐行去分析了,后續我們自己去實現Spring IoC功能時,再詳細的去說一下它底層的一個實現。
最后,我給大家做個總結吧!也不知道大家看不看得懂。
ClassPathXmlApplicationContext對bean配置資源的載入是從refresh方法開始的。refresh方法是一個模板方法,規定了IoC容器的啟動流程,因為有些邏輯是要交給其子類去實現的。那它是如何對bean配置資源進行載入的呢?ClassPathXmlApplicationContext通過調用其父類AbstractApplicationContext的refresh方法啟動整個IoC容器對bean定義的載入過程。
到此這篇關于Java 自定義Spring框架與Spring IoC相關接口分析的文章就介紹到這了,更多相關Java 自定義Spring框架內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://liayun.blog.csdn.net/article/details/120720685