Java開發學習(六)----DI依賴注入之setter及構造器注入解析

|舊市拾荒|發表於2022-06-27

一、DI依賴注入

首先來介紹下Spring中有哪些注入方式?

我們先來思考

  • 向一個類中傳遞資料的方式有幾種?

    • 普通方法(set方法)

    • 構造方法

  • 依賴注入描述了在容器中建立bean與bean之間的依賴關係的過程,如果bean執行需要的是數字或字串呢?

    • 引用型別

    • 簡單型別(基本資料型別與String)

Spring就是基於上面這些知識點,為我們提供了兩種注入方式,分別是:

  • setter注入

    • 簡單型別

    • 引用型別

  • 構造器注入

    • 簡單型別

    • 引用型別

依賴注入的方式已經介紹完,接下來挨個看下:

二、setter注入

  1. 對於setter方式注入引用型別的方式之前已經介紹過,簡單看下:

  • 在bean中定義引用型別屬性,並提供可訪問的set方法

public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
  • 配置中使用property標籤ref屬性注入引用型別物件

<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
</bean>
​
<bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>

2.1 環境準備

環境準備:

  • 建立一個Maven專案

  • pom.xml新增依賴

  • resources下新增spring的配置檔案

最終專案的結構如下:

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

(1)專案中新增BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl類

public interface BookDao {
    public void save();
}
​
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
}
public interface UserDao {
    public void save();
}
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("user dao save ...");
    }
}
​
public interface BookService {
    public void save();
}
​
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
​
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

(2)resources下提供spring的配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

(3)編寫AppForDISet執行類,載入Spring的IOC容器,並從中獲取對應的bean物件

public class AppForDISet {
    public static void main( String[] args ) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

2.2 注入引用資料型別

需求:在bookServiceImpl物件中注入userDao

1.在BookServiceImpl中宣告userDao屬性

2.為userDao屬性提供setter方法

3.在配置檔案中使用property標籤注入

步驟1:宣告屬性並提供setter方法

在BookServiceImpl中宣告userDao屬性,並提供setter方法

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;
    
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}
步驟2:配置檔案中進行注入配置

在applicationContext.xml配置檔案中使用property標籤注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>
步驟3:執行程式

執行AppForDISet類,檢視結果,說明userDao已經成功注入。

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

2.3 注入簡單資料型別

需求:給BookDaoImpl注入一些簡單資料型別的資料

參考引用資料型別的注入,我們可以推出具體的步驟為:

1.在BookDaoImpl類中宣告對應的簡單資料型別的屬性

2.為這些屬性提供對應的setter方法

3.在applicationContext.xml中配置

思考:

引用型別使用的是<property name="" ref=""/>,簡單資料型別還是使用ref麼?

ref是指向Spring的IOC容器中的另一個bean物件的,對於簡單資料型別,沒有對應的bean物件,該如何配置?

步驟1:宣告屬性並提供setter方法

在BookDaoImpl類中宣告對應的簡單資料型別的屬性,並提供對應的setter方法

public class BookDaoImpl implements BookDao {
​
    private String databaseName;
    private int connectionNum;
​
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }
​
    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }
​
    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}
步驟2:配置檔案中進行注入配置

在applicationContext.xml配置檔案中使用property標籤注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <property name="databaseName" value="mysql"/>
        <property name="connectionNum" value="10"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

說明:

value:後面跟的是簡單資料型別,對於引數型別,Spring在注入的時候會自動轉換,但是不能寫成

<property name="connectionNum" value="abc"/>

這樣的話,spring在將abc轉換成int型別的時候就會報錯。

步驟3:執行程式

執行AppForDISet類,檢視結果,說明userDao已經成功注入。

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

注意:兩個property注入標籤的順序可以任意。

對於setter注入方式的基本使用就已經介紹完了,

  • 對於引用資料型別使用的是<property name="" ref=""/>

  • 對於簡單資料型別使用的是<property name="" value=""/>

三、構造器注入

3.1 環境準備

構造器注入也就是構造方法注入,還是先準備下環境:

  • 建立一個Maven專案

  • pom.xml新增依賴

  • resources下新增spring的配置檔案

這些步驟和前面的都一致,大家可以快速的拷貝即可,最終專案的結構如下:

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

(1)專案中新增BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl類

public interface BookDao {
    public void save();
}
​
public class BookDaoImpl implements BookDao {
    
    private String databaseName;
    private int connectionNum;
    
    public void save() {
        System.out.println("book dao save ...");
    }
}
public interface UserDao {
    public void save();
}
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("user dao save ...");
    }
}
​
public interface BookService {
    public void save();
}
​
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
​
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

(2)resources下提供spring的配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

(3)編寫AppForDIConstructor執行類,載入Spring的IOC容器,並從中獲取對應的bean物件

public class AppForDIConstructor {
    public static void main( String[] args ) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

3.2 構造器注入引用資料型別

接下來,在上面這個環境中來完成構造器注入:

需求:將BookServiceImpl類中的bookDao修改成使用構造器的方式注入。

1.將bookDao的setter方法刪除掉

2.新增帶有bookDao引數的構造方法

3.在applicationContext.xml中配置

步驟1:刪除setter方法並提供構造方法

在BookServiceImpl類中將bookDao的setter方法刪除掉,並新增帶有bookDao引數的構造方法

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
​
    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}
步驟2:配置檔案中進行配置構造方式注入

在applicationContext.xml中配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
</beans>

說明:

標籤<constructor-arg>

  • name屬性對應的值為建構函式中方法形參的引數名,必須要保持一致。

  • ref屬性指向的是spring的IOC容器中其他bean物件。

步驟3:執行程式

執行AppForDIConstructor類,檢視結果,說明bookDao已經成功注入。

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

3.3 構造器注入多個引用資料型別

需求:在BookServiceImpl使用建構函式注入多個引用資料型別,比如userDao

1.宣告userDao屬性

2.生成一個帶有bookDao和userDao引數的建構函式

3.在applicationContext.xml中配置注入

步驟1:提供多個屬性的建構函式

在BookServiceImpl宣告userDao並提供多個引數的建構函式

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;
​
    public BookServiceImpl(BookDao bookDao,UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}
步驟2:配置檔案中配置多引數注入

在applicationContext.xml中配置注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

說明:這兩個<contructor-arg>的配置順序可以任意

步驟3:執行程式

執行AppForDIConstructor類,檢視結果,說明userDao已經成功注入。

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

3.4 構造器注入多個簡單資料型別

需求:在BookDaoImpl中,使用建構函式注入databaseName和connectionNum兩個引數。

參考引用資料型別的注入,我們可以推出具體的步驟為:

1.提供一個包含這兩個引數的構造方法

2.在applicationContext.xml中進行注入配置

步驟1:新增多個簡單屬性並提供構造方法

修改BookDaoImpl類,新增構造方法

public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;
​
    public BookDaoImpl(String databaseName, int connectionNum) {
        this.databaseName = databaseName;
        this.connectionNum = connectionNum;
    }
​
    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}
步驟2:配置完成多個屬性構造器注入

在applicationContext.xml中進行注入配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="databaseName" value="mysql"/>
        <constructor-arg name="connectionNum" value="666"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

說明:這兩個<contructor-arg>的配置順序可以任意

步驟3:執行程式

執行AppForDIConstructor類,檢視結果

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

上面已經完成了建構函式注入的基本使用,但是會存在一些問題:

Java開發學習(六)----DI依賴注入之setter及構造器注入解析

  • 當建構函式中方法的引數名發生變化後,配置檔案中的name屬性也需要跟著變,因為是形參的名字。

  • 這兩塊存在緊耦合,具體該如何解決?

在解決這個問題之前,需要提前說明的是,這個引數名發生變化的情況並不多,所以上面的還是比較主流的配置方式,下面介紹的,大家都以瞭解為主。

方式一:刪除name屬性,新增type屬性,按照型別注入

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
</bean>
  • 這種方式可以解決建構函式形參名發生變化帶來的耦合問題

  • 但是如果構造方法引數中有型別相同的引數,這種方式就不太好實現了

方式二:刪除type屬性,新增index屬性,按照索引下標註入,下標從0開始

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg index="1" value="100"/>
    <constructor-arg index="0" value="mysql"/>
</bean>
  • 這種方式可以解決引數型別重複問題

  • 但是如果構造方法引數順序發生變化後,這種方式又帶來了耦合問題

介紹完兩種引數的注入方式,具體我們該如何選擇呢?

  1. 強制依賴使用構造器進行,使用setter注入有概率不進行注入導致null物件出現

    • 強制依賴指物件在建立的過程中必須要注入指定的引數

  2. 可選依賴使用setter注入進行,靈活性強

    • 可選依賴指物件在建立過程中注入的引數可有可無

  3. Spring框架倡導使用構造器,第三方框架內部大多數採用構造器注入的形式進行資料初始化,相對嚴謹

  4. 如果有必要可以兩者同時使用,使用構造器注入完成強制依賴的注入,使用setter注入完成可選依賴的注入

  5. 實際開發過程中還要根據實際情況分析,如果受控物件沒有提供setter方法就必須使用構造器注入

  6. 自己開發的模組推薦使用setter注入

四、總結

這裡主要講的是Spring的依賴注入的實現方式:

  • setter注入

    • 簡單資料型別

      <bean ...>
          <property name="" value=""/>
      </bean>
    • 引用資料型別

      <bean ...>
          <property name="" ref=""/>
      </bean>
  • 構造器注入

    • 簡單資料型別

      <bean ...>
          <constructor-arg name="" index="" type="" value=""/>
      </bean>
    • 引用資料型別

      <bean ...>
          <constructor-arg name="" index="" type="" ref=""/>
      </bean>
  • 依賴注入的方式選擇上

    • 建議使用setter注入

    • 第三方技術根據情況選擇

 

相關文章