我對控制反轉以及依賴注入的認識

classfly發表於2018-06-18

IoC誕生的歷史

在沒有IoC時,關聯不同模組是通過類例項實現的,程式碼可能是這樣子的:

// 程式碼清單1
public interface YourService {
    void func1();
    
    void func2();
}

// 程式碼清單2
public class MyServiceImpl {
    private YourServiceImpl yourServiceImpl;
    
    public MyServiceImpl() {
        this.yourServiceImpl = new YourServiceImpl();
    }
    
    public void process() {
        // do something
        this.yourServiceImpl.func1(...);
        // do something
        this.yourServiceImpl.func2(...);
    }
}
// 程式碼清單3
public class MyServiceImpl {
    private YourService yourService;
    
    public MyServiceImpl(YourService yourService) {
        this.yourService = yourService;
    }
    
    public void process() {
        // do something
        this.yourService.func1(...);
        // do something
        this.yourService.func2(...);
    }
}

當YourServiceImpl的介面不變時,只需要根據業務需要更換不同的YourService實現類即可。一旦更換實現類時(如將yourServiceImpl更換為NewServiceImpl),MyServiceImpl中所有引用YourService的程式碼都要替換為NewServiceImpl,這嚴重違背了物件導向設計中DIP(Dependence Inversion Principle)原則。

* 上層業務應該依賴抽象,抽象定義了"要做什麼",而不應依賴實現細節"怎麼做"。
* 模組間通過介面建立依賴關係
* 實現類依賴介面或抽象類

IOC實現的原理

IoC: Inversion of Control(依賴反轉), 是針對之前模組間通過實現類建立關聯而言,反轉指”模組實現依賴實現類”->”模組實現依賴(實現類的)介面”。

執行IoC原則後,模組內原本生成例項的語句被替換成介面呼叫(程式碼清單3)。我們知道JVM是在執行時才申請記憶體並生成類例項物件的,執行時系統怎麼知曉具體介面對應的實現是什麼呢?

這時候就輪到反射和DI登場了。DI(Dependency injection)依賴注入,在執行時根據spring的xml配置檔案(有實現類的全路徑名稱)選擇適當的時機構造依賴物件例項並注入到依賴實現類介面的模組中。而如何完成依賴例項物件的構造以及初始化是Spring DI的核心。

// 程式碼清單4
    <bean id="userDAO" class="dao.impl.UserDAOImpl">
        <property name="sellerDAO" ref="sellerDAO" />
    </bean>
    
    <bean id="sellerDAO" class="dao.impl.SellerDAOImpl" />

程式碼清單4中演示了spring的bean配置程式碼。Spring解析bean的xml檔案,拿到bean的類全路徑名稱後呼叫Class.forName(String className)方法從對應的類載入器中獲取類資訊,並呼叫Class.newInstance()方法生成類例項。

此時類例項並未完成初始化,還需要注入類例項的依賴項。Spring解析bean下的property條目,以property name為優先查詢例項類的set方法並匹配入參,如果有則調set方法注入property。此時給模組返回完整的依賴物件。

注入方法

set方法注入

該方式要求被注入的屬性在實現類裡有set方法。set注入支援簡單型別和引用型別。

建構函式注入

// 程式碼清單5
/**
 * Created by fujianbo on 2018/6/17.
 *
 * @author fujianbo
 * @date 2018/06/17
 */
public class UserDAOImpl implements UserDAO {
    private SellerDAO sellerDAO;
    
    public void setSellerDAO(SellerDAO sellerDAO) {
        this.sellerDAO = sellerDAO;
    }
    
    public UserDAOImpl() {
    }
    
    public UserDAOImpl(SellerDAO sellerDAO) {
        this.sellerDAO = sellerDAO;
    }
}

// 程式碼清單6
<bean id="sellerDAO" class="dao.impl.SellerDAOImpl" />
<bean id="userDAO" class="dao.impl.UserDAOImpl">
    <constructor-arg ref="sellerDAO"/>
</bean>

當有多個constructor-arg時,Spring根據傳入的依賴引數找到對應的建構函式而並不關心書寫順序。但如果建構函式只是入參順序不同,定義constructor-arg時需要加index引數,否則Spring建立類例項失敗。

// 程式碼清單7
public class User {
    private String userName;
    private Integer age;

    public User(int age, String userName) {
        this.age = age;
        this.userName = userName;
    }

    public User(String userName, int age) {
        this.userName = userName;
        this.age = age;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

...
// yourConfig.xml
<bean id="user" class="..." >
    <constructor-arg index="0" value="Jack"/>
    <constructor-arg index="1" value="26"/>
</bean>

IoC & DI

以下個人對於這兩個兄弟的理解,如有謬誤還請指正!

IoC

1、IoC(控制反轉)是模組間拜託對類例項依賴的橋樑,它統一管理了類例項物件的建立、初始化、注入以及銷燬過程。

2、模組間依賴實現類的介面,從呼叫者角度來看,多數情況無需關注細節以及感知實現細節的變化,只需關注介面入參和返回值,將OOP上升到面向介面、面向切面程式設計的層次,是工廠模式的昇華(參考引用2)。每個模組相當於一個垂直業務,IoC抽象了模組內部建立類例項的程式碼,依託反射和DI,給出了類例項型別識別解析、初始化、物件管理(spring容器)的解決方案。

3、當模組間不再顯示建立類例項總得有人接下這項”苦差事”吧,這人就是Spring。Spring通過bean配置檔案或者@Autowired, @Component, @Resource(J2EE提供), @Service, @Repository等註解,結合java反射原理生成類例項物件並給模組間的介面賦值。

DI

Spring提供的傳遞模組間依賴的介面的例項的方法,是IoC解決方案的一部分。

引用


相關文章