文章目錄
控制反轉 依賴注入 基本概念 與 Spring IOC 源碼學習
1. Background
1996年,Michael Mattson在一篇有關探討面向對象框架的文章中,首先提出了IOC 這個概念。IOC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”,還有些書籍翻譯成爲“控制反向”或者“控制倒置”。IOC理論提出的觀點大體是這樣的:藉助於“第三方”實現具有依賴關係的對象之間的解耦。
2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那麼到底是“哪些方面的控制被反轉了呢?”,經過詳細地分析和論證後,他得出了答案:“獲得依賴對象的過程被反轉了”。控制被反轉之後,獲得依賴對象的過程由自身管理變爲了由IOC容器主動注入。於是,他給“控制反轉”取了一個更合適的名字叫做“依賴注入(Dependency Injection)”。他的這個答案,實際上給出了實現IOC的方法:注入。所謂依賴注入,就是由IOC容器在運行期間,動態地將某種依賴關係注入到對象之中。
所以,依賴注入(DI)和控制反轉(IOC)是從不同的角度的描述的同一件事情,就是指通過引入IOC容器,利用依賴關係注入的方式,實現對象之間的解耦。
IOC中最基本的技術就是“反射(Reflection)”編程,通俗來講就是根據給出的類名(字符串方式)來動態地生成對象。這種編程方式可以讓對象在生成時才決定到底是哪一種對象。
舉個簡單例子:
Person(人)每天都要吃早餐(食物)。我們可以用如下程序表示
public class Person {
public void eat() {
Food food = new food();
System.out.println("I eat food:{}", food.toString());
}
}
在我們吃飯之前必須先new food()(做飯),要不然就吃不上。
Ioc 會怎麼樣做呢
public class Person {
private Food food;
public void eat() {
System.out.println("I eat food:{}", food.toString());
}
}
我們可以把IOC容器的工作模式看做是工廠模式的昇華,可以把IOC容器看作是一個工廠,這個工廠裏要生產的對象都在配置文件中給出定義,然後利用編程語言的的反射編程,根據配置文件中給出的類名生成相應的對象。從實現來看,IOC是把以前在工廠方法裏寫死的對象生成代碼,改變爲由配置文件來定義,也就是把工廠和對象生成這兩者獨立分隔開來,目的就是提高靈活性和可維護性。
2. IOC實現方式
接下來的問題是如何將依賴的對象準備好呢(依賴注入),常用的有兩種方式:構造方法注入和setter注入
-
構造器注入
public Person(Food food) { this.food = food; }
-
setter注入
public void setFood(Food food) { this.food = food; }
3. Spring IOC
Spring IOC的初始化過程:
IOC要實現卻並不那麼容易。它需要一系列技術要實現。首先它需要知道服務的對象是誰,以及需要爲服務對象提供什麼樣的服務。提供的服務指:要完成對象的構建(即把飯做好),將其送到服務對象即完成對象的綁定(即把飯端到我面前)。
IOC需要實現兩個技術:
- 對象的構建
- 對象的綁定
對於這兩個方面技術的實現具有很多的方式:硬編碼(Ioc 框架都支持),配置文件(重點),註解(簡潔)。但無論哪種方式都是在Ioc容器裏面實現的(我們可以理解爲一個大池子,裏面躺着各種各樣的對象,並能通過一定的方式將它們聯繫起來)spring提供了兩種類型的容器,一個是BeanFactory,一個是ApplicationContext(可以認爲是BeanFactory的擴展),下面我們將介紹這兩種容器如何實現對對象的管理。
3.1 BeanFactory
如果沒有特殊指定,默認採用延遲初始化策略(lazy-load)。只有當客戶端對象需要訪問容器中的某個受管對象的時候,纔對該受管對象進行初始化以及依賴注入操作。所以,相對來說,容器啓動初期速度較快,所需要的資源有限。對於資源有限,並且功能要求不是很嚴格的場景,BeanFactory是比較合適的 IoC容器選擇。
我們先來看一下BeanFactory類的關係圖(如下所示)
有三個很重要的部分:
-
BeanDefinition
實現Bean的定義(即對象的定義),且完成了對依賴的定義 -
BeanDefinitionRegistry
,將定義好的bean,註冊到容器中(此時會生成一個註冊碼) -
BeanFactory
是一個bean工廠類,從中可以取到任意定義過的bean
最重要的部分就是BeanDefinition
,它完成了Bean的生成過程。一般情況下我們都是通過配置文件(xml,properties)的方式對bean進行配置,每種文件都需要實現BeanDefinitionReader
,因此是reader本身現了配置文字到bean對象的轉換過程。
Bean 生命週期
Bean的生成大致可以分爲兩個階段:容器啓動階段和bean實例化階段
容器啓動階段:
-
加載配置文件(通常是xml文件)
-
通過reader生成beandefinition
-
beanDefinition註冊到beanDefinitionRegistry
bean實例化階段:
當某個bean 被 getBean()調用時, bean需要完成初時化,以及其依賴對象的初始化如果bean本身有回調,還需要調用其相應的回調函數。從上面我們也可以知道,beanDefinition
(容器啓動階段)只完成bean的定義,並未完成初始化。初始是通過beanFactory的getBean()時才進行的。
Spring IOC在初始化完成之後,給了我們提供一些方法,讓我們來改變一些bean的定義
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
:使我們可能通過配置文件的形式,配置一些參數PropertyOverrideConfigurer
:則可以覆蓋原本的bean參數CustomEditorConfigurer
:則提供類型轉換支持(配置文件都是string,它需要知道轉換成何種類型)
Bean的初始化過程:
如果你認爲實例化的對象就是通過我們定義的類new出來的就錯了,其實這裏用到了AOP機制,生成了其代理對象(通過反射機制生成接口對象,或者是通過CGLIB生成子對象),具體可以參見在上一篇博客。
bean的具體裝載過程是由beanWrapper
實現的,它繼承了PropertyAccessor
(可以對屬性進行訪問)、PropertyEditorRegistry
和TypeConverter
接口 (實現類型轉換,就上前面說的)。
完成設置對象屬性之後,則會檢查是否實現了Aware
類型的接口,如ApplicationContextAware
;如果實現了,則主動加載。
BeanPostprocessor
可以幫助完成在初始化bean之前或之後 幫我們完成一些必要工作,比如我們在連接數據庫之前將密碼存放在一個加密文件,當我們連接數據庫之前,需要將密碼進行加載解密。只要實現相應的接口即可, 下面是BeanPostprocessor
接口定義:
public interface BeanPostProcessor {
/**
* Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one; if
* {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
/**
* Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding {@code bean instanceof FactoryBean} checks.
* <p>This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other BeanPostProcessor callbacks.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one; if
* {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
在完成postProcessor
之後,則會看對象是否定義了InitializingBean
接口,如果是,則會調用其afterProper
-tiesSet()方法進一步調整對象實例的狀態,這種方式並不常見。spring還提供了另外一種指定初始化的方式,即在bean定義中指定init-method 。
當這一切完成之後,還可以指定對象銷燬的一些回調,比如數據庫的連接池的配置,則銷燬前需要關閉連接等。相應的可以實現DisposableBean
接口或指定destroy-method
3.2 ApplicationContext
ApplicationContext
在 BeanFactory
的基礎上構建,是相對比較高級的容器實現,除了擁有 BeanFactory
的所有支持,ApplicationContext
還提供了其他高級特性,比如事件發佈、國際化信息支持等。
ApplicationContext
所管理 的對象,在該類型容器啓動之後,默認全部初始化並綁定完成。所以,相對於 BeanFactory
來說,ApplicationContext
要求更多的系統資源,同時,因爲在啓動時就完成所有初始化,容器啓動時間較之 BeanFactory
也會長一些。在那些系統資源充足,並且要求更多功能的場景中,ApplicationContext
類型的容器是比較合適的選擇。
具體差異:
-
bean的加載方式
BeanFactory
提供BeanReader
來從配置文件中讀取bean配置。相應的ApplicationContext
也提供幾個讀取配置文件的方式:-
FileSystemXmlApplicationContext
:該容器從 XML 文件中加載已被定義的 bean。在這裏,你需要提供給構造器 XML 文件的完整路徑 -
ClassPathXmlApplicationContext
:該容器從 XML 文件中加載已被定義的 bean。在這裏,你不需要提供 XML 文件的完整路徑,只需正確配置 CLASSPATH 環境變量即可,因爲,容器會從 CLASSPATH 中搜索 bean 配置文件。 -
WebXmlApplicationContext
:該容器會在一個 web 應用程序的範圍內加載在 XML 文件中已被定義的 bean。 -
AnnotationConfigApplicationContext
-
ConfigurableWebApplicationContext
-
-
ApplicationContext
採用的非懶加載方式。它會在啓動階段完成所有的初始化,並不會等到getBean()才執行。所以,相對於
BeanFactory
來說,ApplicationContext
要求更多的系統資源,同時,因爲在啓動時就完成所有初始化,容器啓動時間較之BeanFactory
也會長一些。在那些系統資源充足,並且要求更多功能的場景中,ApplicationContext
類型的容器是比較合適的選擇。
ApplicationContext
還額外增加了三個功能:ApplicationEventPublisher
,ResourceLoader
,MessageResource
,以下爲具體解釋:
3.2.1 ResourceLoader
ResourceLoader
並不能將其看成是Spring獨有的功能,spring IOC只是藉助於ResourceLoader
來實現資源加載。也提供了各種各樣的資源加載方式:
-
DefaultResourceLoader
首先檢查資源路徑是否以classpath:前綴打頭,如果是,則嘗試構造ClassPathResource類 型資源並返回。否則, 嘗試通過URL,根據資源路徑來定位資源 -
FileSystemResourceLoader
它繼承自Default-ResourceLoader,但覆寫了getResourceByPath(String)方法,使之從文件系統加載資源並以 FileSystemResource類型返回 -
ResourcePatternResolver
批量查找的ResourceLoader
spring與ResourceLoader之間的關係
所有ApplicationContext
的具體實現類都會直接或者間接地實現AbstractApplicationContext
, AbstactApplicationContext
依賴了DeffaultResourceLoader
, ApplicationContext
繼承了ResourcePatternResolver
,所到頭來ApplicationContext
的具體實現類都會具有DefaultResourceLoader
和PathMatchingResourcePatterResolver
的功能。這也就是會什麼ApplicationContext
可以實現統一資源定位。
3.2.2 ApplicationEventPublisher Spring事件
Spring的文檔對Event的支持翻譯之後描述如下:
ApplicationContext
通過ApplicationEvent
類和ApplicationListener
接口進行事件處理。 如果將實現ApplicationListener
接口的bean注入到上下文中,則每次使用ApplicationContext
發佈ApplicationEvent
時,都會通知該bean。 本質上,這是標準的觀察者設計模式。
-
ApplicationEvent
:繼承自EventObject,同時是spring的application中事件的父類,需要被自定義的事件繼承。 -
ApplicationListener
:繼承自EventListener,spring的application中的監聽器必須實現的接口,需要被自定義的監聽器實現其onApplicationEvent方法 -
ApplicationEventPublisherAware
:在spring的context中希望能發佈事件的類必須實現的接口,該接口中定義了設置ApplicationEventPublisher的方法,由ApplicationContext調用並設置。在自己實現的ApplicationEventPublisherAware子類中,需要有ApplicationEventPublisher屬性的定義。 -
ApplicationEventPublisher
:spring的事件發佈者接口,定義了發佈事件的接口方法publishEvent。因爲ApplicationContext實現了該接口,因此spring的ApplicationContext實例具有發佈事件的功能(publishEvent方法在AbstractApplicationContext中有實現)。在使用的時候,只需要把ApplicationEventPublisher的引用定義到ApplicationEventPublisherAware的實現中,spring容器會完成對ApplicationEventPublisher的注入。
3.2.3 MessageSource
ApplicationContext
接口擴展了MessageSource
接口,因而提供了消息處理的功能(i18n或者國際化)。與HierarchicalMessageSource
一起使用,它還能夠處理嵌套的消息。
當一個ApplicationContext
被加載時,它會自動在context中查找已定義爲MessageSource
類型的bean。此bean的名稱須爲messageSource。如果找到,那麼所有對上述方法的調用將被委託給該bean。否則ApplicationContext
會在其父類中查找是否含有同名的bean。如果有,就把它作爲MessageSource
。如果它最終沒有找到任何的消息源,一個空的StaticMessageSource
將會被實例化,使它能夠接受上述方法的調用。
Spring目前提供了兩個MessageSource
的實現:ResourceBundleMessageSource
和StaticMessageSource
。它們都繼承NestingMessageSource以便能夠處理嵌套的消息。StaticMessageSource
很少被使用,但能以編程的方式向消息源添加消息。ResourceBundleMessageSource
會用得更多一些.
4. 舉個栗子
4.1 註解掃描
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
</beans>
4.2 component/service/controller註解
@Component
public class Person {
@Resource
private Food food;
public void setFood(Food food) {
this.food = food;
}
}
4.3 bean的前置後置
@Component
public class Person {
@Resource
private Food food;
public setFood(Food food) {
this.food = food;
}
@PostConstruct
public void wash() {
System.out.println("飯前洗手");
}
@PreDestroy
public void brush() {
System.out.println("飯後刷牙");
}
}