1. 程式人生 > >簡明依賴注入(Dependency Injection)

簡明依賴注入(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),關於這部分內容可以參見其他文章。