Spring IoC深入理解
阿新 • • 發佈:2020-07-20
#### 本文相關程式碼(來自[官方原始碼](https://github.com/spring-projects/spring-framework.git "官方原始碼")spring-test模組)請參見[spring-demysify](https://gitee.com/jack541/spring-demysify.git "spring-demysify") org.springframework.mylearntest包下。
### 三種注入方式
1.構造方法注入
```java
public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) 10{
this.newsListener = newsListner;
this.newPersistener = newsPersister;
}
```
構造方法注入。 這種注入方式的優點就是,物件在構造完成之後,即已進入就緒狀態,可以馬上使用。缺點就是,當依賴物件比較多的時候,構造方法的引數列表會比較長。而通過反射構造物件的時候,對相同型別的引數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設定預設值。對於非必須的依賴處理,可能需要引入多個構造方法,而引數數量的變動可能造成維護上的不便。
2.setter 方法注入
```java
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
public IFXNewsListener getNewsListener() {
return newsListener;
}
public void setNewsListener(IFXNewsListener newsListener) {
this.newsListener = newsListener;
}
public IFXNewsPersister getNewPersistener() {
return newPersistener;
}
public void setNewPersistener(IFXNewsPersister newPersistener) {
this.newPersistener = newPersistener;
}
}
```
setter方法注入。因為方法可以命名, 所以setter方法注入在描述性上要比構造方法注入好一些。 另外, setter方法可以被繼承,允許設定預設值,而且有良好的IDE支援。缺點當然就是物件無法在構造完成後馬上進入就緒狀態。
3. 介面注入
FXNewsProvider為了讓IoC Service Provider為其注入所依賴的IFXNewsListener,首先需要實現IFXNewsListenerCallable介面,這個介面會宣告一個injectNewsListner方法(方法名隨意),該方法的引數,就是所依賴物件的型別。這樣, InjectionServiceContainer物件,即對應的IoCService Provider就可以通過這個介面方法將依賴物件注入到被注入物件FXNewsProvider當中。
![](https://oscimg.oschina.net/oscnet/up-9df699f75b5917cfacca7c40e33f32876e5.png)
介面注入。從注入方式的使用上來說,介面注入是現在不甚提倡的一種方式,基本處於“退役狀態”。因為它強制被注入物件實現不必要的介面,帶有侵入性。而構造方法注入和setter方法注入則不需要如此。
### IoC Service Provider的職責
業務物件的構建管理:
在IoC場景中,業務物件無需關心所依賴物件如何構建如何獲得,但這部分工作始終需要有人來做。所以,IoC Service Provider需要將物件的構建邏輯從客戶端那裡剝離出來,以免這部分邏輯汙染業務物件的實現。
業務物件間的依賴繫結:
對於IoC Service Provider來說,這個職責是最艱鉅也是最重要的,這是它的最終使命之所在。如果不能完成這個職責,那麼,無論業務物件如何的“呼喊”,也不會得到依賴物件的任何反應(最常見的倒是會收到一個NullPointerException)。IoC Service Provider 通過結合之前構建和管理的所有業務物件,以及各個業務物件間可以識別依賴關係,將這些物件所依賴的物件注繫結,從而保證每個業務物件在使用的時候,可以處於就緒狀態。
### IoC Service Provider 如何管理物件間的依賴關係
如何記錄物件之間的依賴關係:
- 它可以通過最基本的文字檔案來記錄被注入物件和其依賴物件之間的對應關係;
- 它也可以通過描述性較強的XML檔案格式來記錄對應資訊;
- 它還可以通過編寫程式碼的方式來註冊這些對應資訊;
- 甚至,如果願意,它也可以通過語音方式來記錄物件間的依賴注入關係(“嗨,它要一個這種型別的物件,拿這個給它”)
1. 直接編碼式
當前大部分的IoC容器都應該支援直接編碼方式,比如PicoContainer、 Spring、 Avalon等。
```java
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();
```
通過bind方法將“被注入物件”(由IFXNewsListenerCallable介面新增標誌)所依賴的物件,繫結為容器中註冊過的IFXNewsListener型別的物件例項。容器在返回FXNewsProvider物件例項之前,會根據這個繫結資訊,將IFXNewsListener註冊到容器中的物件例項注入到“被注入物件”——FXNewsProvider中,並最終返回已經組裝完畢的FXNewsProvider物件。
2. 配置檔案方式
```xml
```
3.元資料方式(使用Guice)
```java
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
@Inject
public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister) {
this.newsListener = listener;
this.newPersistener = persister;
}
...
}
```
通過@Inject,我們指明需要IoC Service Provider通過構造方法注入方式,為FXNewsProvider注入其所依賴的物件。至於餘下的依賴相關資訊,在Guice中是由相應的Module來提供的,程式碼清單3-7給出了FXNewsProvider所使用的Module實現。
```java
public class NewsBindingModule extends AbstractModule {
@Override
protected void configure() {
bind(IFXNewsListener.class).to(DowJonesNewsListener.class).in(Scopes.SINGLETON);
bind(IFXNewsPersister.class).to(DowJonesNewsPersister.class).in(Scopes.SINGLETON);
}
}
```
通過Module指定進一步的依賴注入相關資訊之後,我們就可以直接從Guice那裡取得最終已經注入完畢,並直接可用的物件了。
```java
Injector injector = Guice.createInjector(new NewsBindingModule());
FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);
newsProvider.getAndPersistNews();
```
### Spring IoC容器 和 IoC Service Provider之間的關係
Spring的IoC容器是一個IoC Service Provider,但是,這只是它被冠以IoC之名的部分原因,我們不能忽略的是“容器”。 Spring的IoC容器是一個提供IoC支援的輕量級容器,除了基本的IoC支援,它作為輕量級容器還提供了IoC之外的支援。如在Spring的IoC容器之上, Spring還提供了相應的AOP框架支援、企業級服務整合等服務。
![](2020-06-14_201731.png)
![IoC容器和Provider的 關係](https://oscimg.oschina.net/oscnet/up-74392209ce75bfa906c3bd90265ab2d3189.png "IoC容器和Provider的 關係")
### Spring提供了BeanFactory 和 ApplicationContext
BeanFactory
- 基礎型別IoC容器,提供完整的IoC服務支援。如果沒有特殊指定,預設採用延遲初始化策略( lazy-load)。只有當客戶端物件需要訪問容器中的某個受管物件的時候,才對該受管物件進行初始化以及依賴注入操作。所以,相對來說,容器啟動初期速度較快,所需要的資源有限。對於資源有限,並且功能要求不是很嚴格的場景, BeanFactory是比較合適的IoC容器選擇。
ApplicationContext
- ApplicationContext在BeanFactory的基礎上構建,是相對比較高階的容器實現,除了擁有BeanFactory的所有支援, ApplicationContext還提供了其他高階特性,比如事件釋出、國際化資訊支援等,這些會在後面詳述。 ApplicationContext所管理的物件,在該型別容器啟動之後,預設全部初始化並繫結完成。所以,相對於BeanFactory來說, ApplicationContext要求更多的系統資源,同時,因為在啟動時就完成所有初始化,容器啟動時間較之BeanFactory也會長一些。在那些系統資源充足,並且要求更多功能的場景中,ApplicationContext型別的容器是比較合適的選擇。
![關係圖](2020-06-14_202534.png)
作為Spring提供的基本的IoC容器,BeanFactory可以完成作為IoC Service Provider的所有職責,包括業務物件的註冊和物件間依賴關係的繫結。
```java
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansExcep