前言
這是因特奈特上面不知道第幾萬篇講依賴注入(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
),關於這部分內容可以參見其他文章。