簡明依賴注入(Dependency Injection)
前言
這是因特奈特上面不知道第幾萬篇講依賴注入(Dependency Injection)的文章,但是說明白的卻寥寥無幾,這篇文章嘗試控制字數同時不做大多數。
首先,依賴注入的是一件很簡單的事情。
為什麼需要依賴注入
然後,假設我們有一個汽車Car,一個引擎介面Engine,兩個引擎具體實現Level4Engine
,Level5Engine
。汽車可以長這樣:
public class Car{ private Engine e; public Car(){ e = new Level4Engine(); } public void ignite(){ System.out.println() } }
現在要讓汽車點火,簡單:
public static void main(String[] args) {
Car c = new Car();
c.ignite();
}
但是假如我們想要換一個更高階的引擎,我們不得不修改Car
的建構函式:
~~ e = new Level4Engine();
~~
e = new Level5Engine();
然後重新編譯。這就是程式碼的耦合,一方面假如需求不會經常改變,這個汽車只會使用Level4Engine
,那沒問題,這個程式碼很完美。但另一方面,假如引擎有多個,需求會經常改變,我們發現Level4Engine
還不行,需要更高階的,而且新引擎還需要進行一系列複雜配置,那這個耦合就是災難了。只是裝配汽車的血汗工人,懂不了那麼多的。
怎麼進行依賴注入
依賴注入就是為了解決上述問題而生的。用依賴注入的寫法解決上面的問題:
public class Car{ private Engine e; public Car(Engine e){ this.e = e; } public void ignite(){ System.out.println() } } // 也可以使用xml進行配置 @Confignuration public CarFactory{ @Bean public Engine engine(){ var e = new Level5Engine(); e.complexConfig(); return e; } @Bean public Car car(Engine e){ return new Car(e); } }
這裡Car對Engine的依賴被抽了出去。Car不負責建立Engine,也不負責/無能力配置Enging。那麼Engine抽出到了哪?又由誰注入給Car?總不能讓Car對著一個殼子(Engine介面)點火吧。
答案當然是spring。spring把它們抽象為Bean,每個@Bean
都通知spring
嘿我要給你一個新的bean,以後就交給你來管理了。
DI的優勢
這樣既解決了上述"汽車裝配工需要引擎配置知識"的問題,也解決了"更改引擎非常困難"的問題:
- 引擎製造者只關注如何製造出引擎,當現在生產條件不成熟就提供
Level4Engine
,反之就提供Level5Engine
,可以隨時更改並對其進行配置 - 汽車裝配工只關注裝配工作,而不需要配置引擎。
- 每次引擎更改後只需要對這個配置類進行編譯,如果使用xml連編譯也不需要了。
這真的就是依賴注入的全部內容了,不過圍繞依賴注入相關還有很多話題可以討論,下面擴充套件就是兩個。
擴充套件1:使用自動裝配代替手動裝配
演示了在CarFactory
中手動car,還沒完,spring還能更聰明一些,它可以通過自動裝配完成這個配置工作:
@Component
public class Car{
private Engine e;
@Autowired
public Car(Engine e){
this.e = e;
}
public void ignite(){
System.out.println()
}
}
@Component
public class Level5Engine{
public void complexConfig(){
System.out.println("really complex stuff...");
}
}
@Confignuration
@ComponentScan
public class CarFactory{}
CarFactory
的@ComponentScan
告訴spring掃描當前類所在包下面的所有類,如果找到@Component
註解就加入spring bean容器。這裡明顯Car和Level5Engine加入了容器(預設會類名首字母小寫,所以加入的是car
和level5Engine
)。然後@Autowired
在當前容器中查詢,如果找到需要注入的型別就自動注入:
@Autowired
public Car(Engine e){
this.e = e;
}
Car的裝配需要一個引擎,spring容器剛好有一個實現了Engine的Level5Engine引擎,所以這裡自動注入。
擴充套件2: NoUniqueBeanDefinitionException自動裝配歧義
最後一個不常見的問題,假如我們把兩個引擎都標註了@Component
會怎麼樣:
@Component
public class Level5Engine{
}
@Component
public class Level4Engine{
}
spring不知道用哪一個注入給car,所以丟擲NoUniqueBeanDefinitionException
,表示有多個候選注入物件,需要我們手動縮小範圍(@Qualifier
,@Component value
,@Primary
),關於這部分內容可以參見其他文章。