一、Spring IoC容器概述 1.依賴反轉(依賴註入):依賴對象的獲得被反轉了。 如果合作對象的引用或依賴關係的管理由具體對象來完成,會導致代碼的高度耦合和可測試性的降低,這對複雜的面向對象系統的設計是非常不利的。 在Spring中,IoC容器是實現依賴控制反轉這個模式的載體,它可以在對象生成或 ...
一、Spring IoC容器概述
1.依賴反轉(依賴註入):依賴對象的獲得被反轉了。
如果合作對象的引用或依賴關係的管理由具體對象來完成,會導致代碼的高度耦合和可測試性的降低,這對複雜的面向對象系統的設計是非常不利的。
在Spring中,IoC容器是實現依賴控制反轉這個模式的載體,它可以在對象生成或者初始化時直接將數據註入到對象中,也可以通過將對象引用註入到對象數據域中的方式來註入對方法調用的依賴。這種依賴是可以遞歸的,對象被逐層註入。
關於如何反轉對依賴的控制,把控制權從具體業務對象中轉交到平臺或者框架中,是降低面向對象系統設計複雜性和提高面向對象系統可測試性的一個有效的解決方案。它促進IoC設計模式的發展,是IoC容器要解決的核心問題。
具體依賴註入的主要實現方式:介面註入(Type 1 IoC)、setter註入(Type 2 IoC)、構造器註入(Type 3 IoC),在Spring的IoC設計中,setter註入和構造器註入是主要的註入方式,相對而言,使用Spring時setter註入是常見的註入方式,而且為了防止註入異常,Spring IoC容器還提供了對特定依賴的檢查。
二、IoC容器系列的設計與實現:BeanFactory和ApplicationContext
BeanFactory簡單容器系列:這系列容器只實現了容器的最基本功能;
ApplicationContext高級容器系列:ApplicationContext應用上下文,作為同期的高級形態存在。應用上下文在簡單容器的基礎上,增加了許多面向框架的特性,同時對應用環境做了許多適配。
IoC容器是用來管理對象依賴關係的,對IoC容器來說,BeanDefinition就是對依賴反轉模式中管理的對象依賴關係的數據抽象,也是容器實現依賴反轉功能的核心數據結構,依賴反轉功能都是圍繞對這個BeanDefinition的處理來完成的。
上圖是IoC容器的介面設計圖,從圖中我們可以看到,IoC容器主要有兩種設計路徑:
1.從介面BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一條主要的BeanFactory設計路徑。在這條介面設計路徑中,BeanFactory介面定義了基本的IoC容器規範。在這個介面定義中,包括了getBean()這樣的IoC容器的基本方法(通過這個方法可以從容器中取得Bean)。
2.第二條介面設計主線是,以ApplicationContext應用上下文介面為核心的介面設計,這裡涉及的主要介面設計有,從BeanFactory到ListableBeanFactory,再到ApplicationContext,再到我們常用的WebApplicationContext或者ConfigurableApplicationContext介面。對於ApplicationContext介面,它通過繼承MessageSource、ResourceLoader、ApplicationEventPublisher介面,在BeanFactory簡單IoC容器的基礎上添加了許多對高級容器的特性支持。
(一)、BeanFactory
BeanFactory介面定義了IoC容器最基本的形式,並且提供了IoC容器所應該遵守的最基本的服務契約,同時,這也是我們使用IoC容器所應遵守的最底層和最基本的編程規範,這些介面定義勾出了IoC的基本輪廓。
BeanFactory和FactoryBean是在Spring中使用頻率很高的類。它們在拼寫上非常相似。一個是Factory,也就是IoC容器或者對象工廠;一個是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IoC容器)來進行管理的。但對FactoryBean而言,這個Bean不是簡單的Bean,而是一個能產生或者修飾對象生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。
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 = "&"; Object getBean(String var1) throws BeansException; <T> T getBean(String var1, @Nullable 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; boolean containsBean(String var1); boolean isSingleton(String var1) throws NoSuchBeanDefinitionException; boolean isPrototype(String var1) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String var1) throws NoSuchBeanDefinitionException; String[] getAliases(String var1); }
通過BeanFactory介面的定義,用戶可以執行以下操作:
1.通過介面方法getBean獲取Bean,還可以通過參數方法對Bean類型進行檢查;
2.通過介面方法containsBean讓用戶能夠判斷容器是否含有制定名字的Bean;
3.通過介面方法isSingleton來查詢指定名字的Bean是否是Singleton類型的Bean。對於Singleton屬性,用戶可以在BeanDefinition中指定;
4.通過介面方法isPrototype來查詢指定名字的Bean是否是prototype類型的。與Singleton屬性一樣,這個屬性也可以由用戶在BeanDefinition中指定;
5.通過介面方法isTypeMatch來查詢指定了名字的Bean的Class類型是否是特定的Class類型。這個Class類型可以由用戶指定;
6.通過介面方法getType來查詢指定名字的Bean的Class類型;
7.通過介面方法getAliases來查詢指定了名字的Bean的所有別名,這些別名都是用戶在BeanDefinition中定義的;
這些定義的介面方法勾畫出了IoC容器的基本特性。
為了更清楚地瞭解BeanFactory作為容器的工作原理,我們來看一下BeanFanctory的一個實現類XmlBeanFactory的源代碼:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.beans.factory.xml; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.io.Resource; /** @deprecated */ @Deprecated public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader; public XmlBeanFactory(Resource resource) throws BeansException { this(resource, (BeanFactory)null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader = new XmlBeanDefinitionReader(this); this.reader.loadBeanDefinitions(resource); } }
我們看到XmlBeanFactory是用了DefaultListableBeanFactory作為基類,DefaultListableBeanFactory是很重要的一個IoC實現,在其他IoC容器中,比如ApplicationContext,其實現的基本原理和XmlBeanFactory一樣,也是通過持有或者擴展DefaultListableBeanFactory來獲得基本的IoC容器的功能的。
參考XmlBeanFactory的實現,我們以編程的方式使用DefaultListableBeanFactory。從中我們可以看到IoC容器使用的一些基本過程。
package com.xyfer.controller; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; public class IoCDemo { public static void main(String[] args) { ClassPathResource res = new ClassPathResource("demo.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res); } }
這樣,我們就可以通過factory對象來使用DefaultListableBeanFactory這個IoC容器來。在使用IoC容器時,需要如下幾個步驟:
1.創建IoC配置文件的抽象資源,這個抽象資源包含了BeanDefinition的定義信息;
2.創建一個BeanFactory,這裡使用DefaultListableBeanFactory;
3.創建一個載入BeanDefinition的讀取器,這裡使用XmlBeanDefinitionReader來載入XML文件形式的BeanDefinition,通過一個回調配置給BeanFactory;
4.從定義好的資源位置讀入配置信息,具體的解析過程由XmlBeanDefinitionReader來完成。完成整個載入和註冊Bean定義之後,需要的IoC容器就建立起來了。這個時候就可以直接使用IoC容器了。
(二)、ApplicationContext
ApplicationContext除了提供BeanFactory提供的容器的基本功能外,還為用戶提供了以下的附加服務,所以說ApplicationContext是一個高級形態意義的IoC容器。
從ApplicationContext繼承關係中,可以看到ApplicationContext在BeanFactory的基礎上通過實現不同的介面而添加不同的附加功能。
1.支持不同的信息源。ApplicationContext擴展了MessageSource介面,這些信息源的擴展功能可以支持國際化的實現,為開發多語言版本的應用提供服務。
2.訪問資源。這一特性體現在對ResourceLoader和Resource的支持上,這樣我們可以從不同的地方得到Bean定義資源。
3.支持應用事件。繼承了介面ApplicationEventPublisher,從而在上下文中引入了事件機制。這些事件和Bean的生命周期的結合為Bean的管理提供了便利。
4.在ApplicationContext中提供的附加服務。這些服務使得基本IoC容器的功能更豐富。一般建議在開發應用時使用ApplicationContext作為IoC容器的基本形式。
三、IoC容器的初始化過程
簡單來說,IoC容器的初始化是由refresh()方法啟動的,這個方法標誌IoC容器的正式啟動。具體來說,這個啟動包括BeanDefinition的Resource定位、載入和註冊三個基本過程。
1.Resource定位過程。Resource定位指的是BeanDefinition的資源定位,它由ResourceLoader通過統一的Resource介面來完成,這個Resource對各種形式的BeanDefinition的使用都提供來統一的介面。在文件系統中的Bean定義信息可以使用FileSystemResource來進行抽象;在類路徑中的Bean定義信息可以使用ClassPathResource來抽象。
2.BeanDefinition的載入。這個載入過程是把用戶定義好的Bean表示成IoC容器內部的數據結構,而這個容器內部的數據結構就是BeanDefinition。具體來說,這個BeanDefinition實際上就是POJO對象在IoC容器中的抽象,通過這個BeanDefinition定義的數據結構,使IoC容器能夠方便地對POJO對象也就是Bean進行管理。
3.向IoC容器註冊這些BeanDefinition的過程。這個過程是通過調用BeanDefinitionRegistry介面的實現來完成的。這個註冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行註冊。通過分析,我們可以看到,在IoC容器內部將BeanDefinition註入到一個HashMap中去,IoC容器就是通過這個HashMap來持用這些BeanDefinition數據的。
這裡談的是IoC容器初始化過程,這個過程一般不包含Bean依賴註入的實現。在Spring IoC的設計中,Bean定義的載入和依賴註入是兩個獨立的過程。依賴註入一般發生在應用第一次通過getBean向容器索取Bean的時候。但是又一個例外的配置,在使用IoC容器時有一個預實例化的配置,通過這個預實例化的配置(具體來說,可以通過為Bean定義信息中的lazyinit屬性),可以對容器初始化過程做一個微小的控制,從而改變這個被設置了lazyinit屬性的Bean的依賴註入過程。舉例來說,如果我們對某個Bean設置了lazyinit屬性,那麼這個Bean的依賴註入在IoC容器初始化時就預先完成了,而不需要等到整個初始化完成以後,第一次使用getBean時才會觸發。
四、IoC容器的依賴註入
IoC容器的初始化過程完成的主要工作在IoC容器中建立BeanDefinition數據映射。但是在此過程中IoC容器並沒有對Bean的依賴關係進行註入。
當IoC容器已經載入了用戶定義的Bean信息,容器中的BeanDefinition數據已經建立好的前提下,依賴註入的過程是在用戶第一次向IoC容器索要Bean時觸發的,也就是第一次調用getBean的時候觸發,當然也有例外,就是當在BeanDefiniton中設置lazyinit屬性來讓容器完成對Bean的預實例化。這個預實例化實際上也是一個完成依賴註入的過程,但是這個依賴註入的過程是在初始化的過程中完成的。
getBean是依賴註入的起點,之後會調用createBean,Bean對象會依據BeanDefinition定義的要求生成。createBean不但生成了需要的Bean,還對Bean初始化進行了處理,比如實現了在BeanDefinition中的init-method屬性定義,Bean後置處理器等。CGLIB是一個常用的位元組碼生成器的類庫,它提供了一系列的API來提供生成和轉換JAVA的位元組碼的功能。在Spring AOP中也使用CGLIB對JAVA的位元組碼進行增強。在IoC容器中,Spring通過預設類SimpleInstantiationStrategy類來生成Bean對象,它提供了兩種實例化Java對象的方法,一種是通過BeanUtils,它使用了JVM的反射功能,一種是通過CGLIB來生成。
在實例化Bean對象生成的基礎上,接下來就是各種依賴關係的處理。通過對BeanDefinition中的對象、value值、List、Map等進行解析,然後使用反射對屬性進行註入。
在Bean的創建和對象依賴註入的過程中,使用遞歸在上下文體系中查找需要的Bean和創建Bean;在依賴註入時,通過遞歸調用容器的getBean方法,得到當前Bean的依賴Bean,同時也觸發對依賴Bean的創建和註入。在對Bean的屬性進行依賴註入時,解析的過程也是遞歸的過程。這樣,根據依賴關係,一層一層地完成Bean的創建和註入,直到最後完成當前Bean的創建。有了這個頂層Bean的創建和對它的屬性依賴註入的完成,意味著和當前Bean相關的整個依賴鏈的註入也完成了。
五、IoC容器的其他相關特性
1.ApplicationContext和Bean的初始化及銷毀
Bean的生命周期
(1)Bean實例的創建
(2)為Bean實例設置屬性
(3)調用Bean的初始化方法
(4)應用可以通過IoC容器使用Bean
(5)當容器關閉時,調用Bean的銷毀方法
2.lazy-init屬性和預實例化
3.FactoryBean的實現
4.BeanPostProcessor的實現
5.autowiring(自動依賴裝配)的實現
配置autowiring屬性,IoC容器會根據這個屬性的配置,使用反射自動查找屬性的類型或者名字,然後基於屬性的類型或名字來自動匹配IoC容器中的Bean,從而自動地完成依賴註入。
6.Bean的依賴檢查
Spring通過依賴檢查特性,幫助應用檢查是否所有的屬性都已經被正確設置。在Bean定義時設置dependency-check屬性來指定依賴檢查模式即可。屬性可以設置為none、simple、object、all四種模式,預設的模式是none。