簡明依賴注入(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加入了容器(默認會類名首字母小寫,所以加入的是carlevel5Engine)。然後@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),關於這部分內容可以參見其他文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章