理解Spring中依賴注入(DI)與控制反轉(zhuǎn)(IoC)

相關(guān)概念

依賴注入(Dependency Injection,簡稱DI)與控制反轉(zhuǎn)(IoC)的含義相同,只不過這兩個稱呼是從兩個角度描述的同一個概念。對于一個Spring初學者來說,這兩種稱呼很難理解,下面我們將通過簡單的語言來描述這兩個概念。

當某個Java對象(調(diào)用者)需要調(diào)用另一Java對象(被調(diào)用者,即被依賴對象)時,在傳統(tǒng)模式下,調(diào)用者通常會采用“new被調(diào)用者”的代碼方式來創(chuàng)建對象。這種方式會導致調(diào)用者與被調(diào)用者之間的耦合性增加,不利于后期項目的升級和維護。

在使用Spring框架之后,對象的實例不再由調(diào)用者來創(chuàng)建,而是由Spring容器來創(chuàng)建,Spring容器會負責控制程序之間的關(guān)系,而不是由調(diào)用者的程序代碼直接控制。這樣,控制權(quán)由應(yīng)用代碼轉(zhuǎn)移到了Spring容器,控制權(quán)發(fā)生了反轉(zhuǎn),這就是Spring的控制反轉(zhuǎn)。

從Spring容器的角度來看,Spring容器負責將被依賴對象賦值給調(diào)用者的成員變量,這相當于為調(diào)用者注入了它依賴的實例,這就是Spring的依賴注入。

相對于“控制反轉(zhuǎn)”,“依賴注入”的說法也許更容易理解一些,即由容器(如Spring)負責把組件所“依賴”的具體對象“注入”(賦值)給組件,從而避免組件之間以硬編碼的方式結(jié)合在一起。


依賴注入的實現(xiàn)方式

依賴注入的作用就是在使用Spring框架創(chuàng)建對象時,動態(tài)地將其所依賴的對象注入Bean組件中,其實現(xiàn)方式通常有兩種,一種是屬性setter方法注入,另一種是構(gòu)造方法注入,具體介紹如下。


屬性setter方法注入 指IoC容器使用setter方法注入被依賴的實例。通過調(diào)用無參構(gòu)造器或無參靜態(tài)工廠方法實例化Bean后,調(diào)用該Bean的setter方法,即可實現(xiàn)基于setter方法的依賴注入。

構(gòu)造方法注入指IoC容器使用構(gòu)造方法注入被依賴的實例?;跇?gòu)造方法的依賴注入通過調(diào)用帶參數(shù)的構(gòu)造方法來實現(xiàn),每個參數(shù)代表著一個依賴。


了解了兩種注入方式后,上一示例就是以屬性setter方法注入的方式為例,下面修改上述案例,使用構(gòu)造方法在Spring容器在應(yīng)用中是如何實現(xiàn)依賴注入的。

(1)在MyEclipse中,創(chuàng)建一個Java項目,將Spring的4個基礎(chǔ)包以及commons-logging的JAR包復(fù)制到lib目錄中,并發(fā)布到類路徑下,與上一項目基礎(chǔ)配置相同。

(2)在src目錄下,創(chuàng)建一個cn.springdemo包,并在包中創(chuàng)建HelloSpring.java,為期添加無參構(gòu)造方法、有參構(gòu)造方法,然后在類中定義一個print()方法,示例代碼如下:

【示例】HelloSpring.java


1 public class HelloSpring {

2?? ? // 定義who屬性,該屬性的值將通過Spring框架進行設(shè)置

3?? ? private String who = null;

4?

5 ????/**

6???????? * 定義打印方法,輸出一句完整的問候。

7???????? */

8 ????????public void print() {

9 ????????????System.out.println("Hello," + who + "!");

10???????? }

11

12 ??????public HelloSpring() {

13??????????super();

14 ??????}

15

16 ??????public HelloSpring(String who) {

17 ??????? ??super();

18?????????? this.who = who;

19?????? }

20 }


(3)在resources目錄下,編寫Spring配置文件,在Spring配置文件中修改id為helloSpring的Bean為HelloSpring類的實例,并通過構(gòu)造方法為who屬性注入屬性值。Spring配置文件內(nèi)容示例代碼如下:

【示例】applicationContext.xml

1 <?xml version="1.0" encoding="UTF-8"?>

2 <beans xmlns="http://www.springframework.org/schema/beans"

3???? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

4 ????xsi:schemaLocation="http://www.springframework.org/schema/beans

5 ????http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

6 ????<bean id="helloSpring" class="cn.springdemo.HelloSpring">

7 ????????<!-- 通過定義的單參構(gòu)造為helloSpring的who屬性賦值 -->

8 ????????<constructor-arg index="0" value="Spring" />

9 ????</bean>

10 </beans>


(4)在cn.test包下,創(chuàng)建測試類HelloTest,并在類中編寫test()方法,示例代碼如下:

【示例】HelloTest.java

1 @Test

2 public void test() {

3? ?? // 通過ClassPathXmlApplicationContext實例化Spring的上下文

4???? ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

5? ? // 通過ApplicationContext的getBean()方法,根據(jù)id來獲取bean的實例

6? ? ?HelloSpring helloSpring = (HelloSpring) context.getBean("helloSpring");

7? ? ?// 執(zhí)行print()方法

8? ? ?helloSpring.print();

9 }


執(zhí)行程序后,控制臺的輸出結(jié)果與之前屬性setter方法注入效果一致,注意兩個HelloSpring.java的差異,使用屬性setter方法注入必須要為屬性提供getter方法實現(xiàn)屬性值得注入,使用構(gòu)造方法注入必須要為類提供對應(yīng)參數(shù)屬性值的構(gòu)造實現(xiàn)才能注入值。

理解“控制反轉(zhuǎn)”

控制反轉(zhuǎn)(Inversion of Control,IoC),也稱為依賴注入(Dependency Injection,DI),是面向?qū)ο缶幊讨械囊环N設(shè)計理念,用來降低程序代碼之間的耦合度,在MVC的設(shè)計模式中經(jīng)常使用。首先考慮什么是依賴。依賴,在代碼中一般指通過局部變量、方法參數(shù)、返回值等建立的對于其他對象的調(diào)用關(guān)系。例如,在A類的方法中,實例化了B類的對象并調(diào)用其方法以完成特定的功能,我們就說A類依賴于B類。


幾乎所有的應(yīng)用都是由兩個或更多的類通過彼此合作來實現(xiàn)完整的功能。類與類之間的依賴關(guān)系增加了程序開發(fā)的復(fù)雜程度,我們在開發(fā)一個類的時候,還要考慮對正在使用該類的其他類的影響。

例如,常見的業(yè)務(wù)層調(diào)用數(shù)據(jù)訪問層實現(xiàn)持久化操作,解決問題的步驟如下:

(1)獲取Spring開發(fā)包并為工程添加Spring支持。

(2)為業(yè)務(wù)層和數(shù)據(jù)訪問層設(shè)計接口,聲明所需方法。

(3)編寫數(shù)據(jù)訪問層接口UserDao的實現(xiàn)類,完成具體的持久化操作。

(4)在業(yè)務(wù)實現(xiàn)類中聲明UserDao接口類型的屬性,并添加適當?shù)臉?gòu)造方法為屬性賦值。

(5)在Spring的配置文件中將DAO對象以構(gòu)造注入的方式賦值給業(yè)務(wù)實例中的UserDao類型的屬性。

(6)在代碼中獲取Spring配置文件中裝配好的業(yè)務(wù)類對象,實現(xiàn)程序功能。


實現(xiàn)步驟如下:

(1)在MyEclipse中,創(chuàng)建一個Java項目,在該項目的lib目錄中加入Spring支持和依賴的JAR包。

(2)為業(yè)務(wù)層調(diào)用數(shù)據(jù)訪問層實現(xiàn)持久化操作,如示例所示。

【示例】UserDao.java

1 /**

2? * 增加DAO接口,定義了所需的持久化方法

3? */

4 public interface UserDao {

5? ? ?public void save(User user);

6 }


【示例】UserDaoImpl.java

1 /**

2? * 用戶DAO類,實現(xiàn)IDao接口,負責User類的持久化操作

3? */

4 public class UserDaoImpl implements UserDao {

5 ????public void save(User user) {

6???????? // 這里并未實現(xiàn)完整的數(shù)據(jù)庫操作,僅為說明問題

7???????? System.out.println("保存用戶信息到數(shù)據(jù)庫");

8???? }

9 }


【示例】UserService.java

1 /**

2? * 用戶業(yè)務(wù)接口,定義了所需的業(yè)務(wù)方法

3? */

4 public interface UserService {

5 ????public void addNewUser(User user);

6 }


【示例】UserServiceImpl.java

1 /**

2? * 用戶業(yè)務(wù)類,實現(xiàn)對User功能的業(yè)務(wù)管理

3? */

4 public class UserServiceImpl implements UserService {

5 ????// 聲明接口類型的引用,和具體實現(xiàn)類解耦合

6 ????private UserDao userDao;

7?

8 ????// userDao 屬性的setter訪問器,會被Spring調(diào)用,實現(xiàn)設(shè)值注入

9???? public UserDao getUserDao() {

10 ????????return userDao;

11 ????}

12 ????public void setUserDao(UserDao userDao) {

13 ????????this.userDao = userDao;

14???? }

15 ????public void addNewUser(User user) {

16 ????????// 調(diào)用用戶DAO的方法保存用戶信息

17 ????????userDao.save(user);

18 ????}

19 }


如以上代碼所示,UserServiceImpl對UserDaoImpl存在依賴關(guān)系。這樣的代碼很常見,但是存在一個嚴重的問題,即UserServiceImpl和UserDaoImpl高度耦合,如果因為需求變化需要替換UserDao的實現(xiàn)類,將導致UserServiceImpl中的代碼隨之發(fā)生修改。如此,程序?qū)⒉痪邆鋬?yōu)良的可擴展性和可維護性,甚至在開發(fā)中難以測試。


(3)這里我們改為使用Spring的IoC的方式實現(xiàn),在配置文件applicationContext.xml 中,創(chuàng)建一個id為UserService的Bean,該Bean用于實例化UserServiceImpl類的信息,并將userDao的實例注入到UserService中,其代碼如下所示。

【示例】applicationContext.xml

1 <?xml version="1.0" encoding="UTF-8"?>

2 <beans xmlns="http://www.springframework.org/schema/beans"

3???? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

4 ????xsi:schemaLocation="http://www.springframework.org/schema/beans

5???? http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

6 ????<!--添加一個id為userService的實例 -->

7 ????<bean id="userDao" class="cn.dsscm.dao.UserDaoImpl" />

8???? <!--添加一個id為userService的實例 -->

9???? <bean id="userService" class="cn.dsscm.service.UserServiceImpl">

10???? <!-- 將id為userDao的Bean實例注入到userService實例中 -->

11? ????? <property name="userDao" ref="userDao" />

12 ????</bean>

13 </beans>


在上述代碼中,<property>是<bean>元素的子元素,它用于調(diào)用Bean實例中的setUserDao()方法完成屬性賦值,從而實現(xiàn)依賴注入。其name屬性表示Bean實例中的相應(yīng)屬性名,ref屬性用于指定其屬性值。


(4)在cn.dsscm.test包中,創(chuàng)建測試類IoCTest,來對程序進行測試,編輯后其代碼如下所示。

【示例】IoCTest.java

1 import org.junit.Test;

2 import org.springframework.context.ApplicationContext;

3 import org.springframework.context.support.ClassPathXmlApplicationContext;

4?

5 import cn.dsscm.pojo.User;

6 import cn.dsscm.service.UserService;

7?

8 public class IoCTest {

9? ? ?@Test

10? ? ?public void test() {

11? ? ? ? ?// 通過ClassPathXmlApplicationContext實例化Spring的上下文

12? ? ? ? ?ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

13?? ? ? ?// 通過ApplicationContext的getBean()方法,根據(jù)id來獲取bean的實例

14? ? ? ? ?UserService userService =? (UserService) context.getBean("userService");

15? ? ? ? ?// 執(zhí)行print()方法

16?? ? ? ?userService.addNewUser(new User());

17? ? ?}

18 }


執(zhí)行程序后,控制臺的輸出結(jié)果.

可以看出,使用Spring容器通過UserService實現(xiàn)類中的addNewUser()方法,調(diào)用了UserDao實現(xiàn)類中的addNewUser()方法,并輸出了結(jié)果。這就是Spring容器屬性setter注入的方式,也是實際開發(fā)中最為常用的一種方式。

分析其使用“控制反轉(zhuǎn)”方式,其利用簡單工廠和工廠方法模式的思路分析此類問題,其代碼如下所示。


【示例】簡單工廠和工廠方法模式

1 /**

2 *增加用戶DAO工廠,負責用戶DAO實例的創(chuàng)建工作

3 */

4 public class UserDaoFactory {

5 //負責創(chuàng)建用戶DAO實例的方法

6? ?public static UserDao getInstance() {

7? ? ? //具體實現(xiàn)過程略

8? ? }

9 }

10?

11 /**

12? * 用戶業(yè)務(wù)類,實現(xiàn)對User功能的業(yè)務(wù)管理

13? */

14 public class UserServiceImpl implements UserService {

15???? private UserDao dao = UserDaoFactory.getInstance();

16???? public void addNewUser(User user) {

17 ????????// 調(diào)用用戶DAO的方法保存用戶信息

18 ????????dao.save(user);

19 ????}

20 }

這里的用戶DAO工廠類UserDaoFactory體現(xiàn)了"控制反轉(zhuǎn)"的思想:UserServiceImpl不再依靠自身的代碼去獲得所依賴的具體DAO對象,而是把這一工作轉(zhuǎn)交給了"第三方"——UserDaoFactory,從而避免了和具體UserDao實現(xiàn)類之間的耦合。由此可見,在如何獲取所依賴的對象這件事上,“控制權(quán)"發(fā)生了"反轉(zhuǎn)”——從UserServiceImpl轉(zhuǎn)移到了UserDaoFactory,這就是所謂的"控制反轉(zhuǎn)"。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容