Spring中bean的四種注入方式

特務依昂發表於2020-05-12

一、前言

  最近在複習Spring的相關內容,這篇部落格就來記錄一下Springbean的屬性注入值的四種方式。這篇部落格主要講解在xml檔案中,如何為bean的屬性注入值,最後也會簡單提一下使用註解的方式。廢話不多說,直接開始吧。


二、正文

2.1 注入方式

  在Spring中,共有四種方式為bean的屬性注入值,分別是:

  • set方法注入
  • 構造器注入
  • 靜態工廠注入
  • 例項工廠注入

  下面我就分別演示一下,如何使用這四種方式進行屬性的注入。


2.2 set方法注入

  在演示前,我們需要準備幾個類,我使用下面兩個類來進行注入的演示,這兩個類分別是UserCar類:

public class Car {
    // 只包含基本資料型別的屬性
    private int speed;
    private double price;
    
    public Car() {
    }
    public Car(int speed, double price) {
        this.speed = speed;
        this.price = price;
    }
    
    public int getSpeed() {
        return speed;
    }
    public void setSpeed(int speed) {
        this.speed = speed;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Car{" +
                "speed=" + speed +
                ", price=" + price +
                '}';
    }
}

public class User {
	
    private String name;
    private int age;
    // 除了上面兩個基本資料型別的屬性,User還依賴Car
    private Car car;
    
    public User() {
    }
    public User(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}

  有了上面兩個類,我們就可以演示set注入了。需要注意一點,如果我們需要使用set注入,那麼必須要為屬性提供set方法,Spring容器就是通過呼叫beanset方法為屬性注入值的。而在xml檔案中,使用set注入的方式就是通過property標籤,如下所示:

<!-- 定義car這個bean,id為myCar -->
<bean id="myCar" class="cn.tewuyiang.pojo.Car">
    <!-- 
        為car的屬性注入值,因為speed和price都是基本資料型別,所以使用value為屬性設定值;
        注意,這裡的name為speed和price,不是因為屬性名就是speed和price,
        而是set方法分別為setSpeed和setPrice,名稱是通過將set刪除,然後將第一個字母變小寫得出;
    -->
    <property name="speed" value="100"/>
    <property name="price" value="99999.9"/>
</bean>

<!-- 定義user這個bean -->
<bean id="user" class="cn.tewuyiang.pojo.User">
    <property name="name" value="aaa" />
    <property name="age" value="123" />
    <!-- car是引用型別,所以這裡使用ref為其注入值,注入的就是上面定義的myCar 
         基本資料型別或Java包裝型別使用value,
         而引用型別使用ref,引用另外一個bean的id 
    -->
    <property name="car" ref="myCar" />
</bean>

  通過上面的配置,就可以為CarUser這兩個型別的bean注入值了。需要注意的是,property的name屬性,填寫的不是屬性的名稱,而是set方法去除set,然後將第一個字元小寫後的結果。對於基本資料型別,或者是Java的包裝型別(比如String),使用value注入值,而對於引用型別,則使用ref,傳入其他bean的id。接下來我們就可以測試效果了:

@Test
public void test1() {
    ApplicationContext context =
        new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    // 獲取user這個bean
    User user = context.getBean(User.class);
    // 輸出產看結果
    System.out.println(user);
}

  由於user包含car的引用,所以我們直接輸出user,也能夠看到car的情況,輸入結果如下:

User{name='aaa', age=123, car=Car{speed=100, price=99999.9}}

2.3 構造器注入

  下面我們來說第二種方式——構造器注入。聽名字就可以知道,這種注入值的方式,就是通過呼叫bean所屬類的帶參構造器為bean的屬性注入值。這也就意味著,我們如果需要使用構造器注入,就得為類提供包含引數的構造方法。構造器注入,實際上有多種匹配屬性值的方式,下面我們就來一一列舉。我們這裡依然使用2.2中定義的CarUser這兩個類,測試方法以及類的定義都不需要變,需要改變的僅僅是xml配置檔案。

(一)匹配構造器的引數名稱

  我們需要通過constructor-arg標籤為構造器傳入引數值,但是每個constructor-arg標籤對應哪一個引數值呢?這就有多種方式指定了。第一種就是直接匹配引數名,配置如下:

<bean id="myCar" class="cn.tewuyiang.pojo.Car">
    <!-- 通過constructor-arg的name屬性,指定構造器引數的名稱,為引數賦值 -->
    <constructor-arg name="speed" value="100" />
    <constructor-arg name="price" value="99999.9"/>
</bean>

<bean id="user" class="cn.tewuyiang.pojo.User">
    <constructor-arg name="name" value="aaa" />
    <constructor-arg name="age" value="123" />
    <!-- 
         和之前一樣,基本資料型別或Java包裝型別使用value,
         而引用型別使用ref,引用另外一個bean的id 
    -->
    <constructor-arg name="car" ref="myCar" />
</bean>

  這樣就完成了,測試程式碼和之前一樣,執行結果也一樣,我這裡就不貼出來了。有人看完之後,可能會覺得這裡的配置和set注入時的配置幾乎一樣,除了一個使用property,一個使用constructor-arg。確實,寫法上一樣,但是表示的含義卻完全不同。property的name屬性,是通過set方法的名稱得來;而constructor-arg的name,則是構造器引數的名稱


(二)匹配構造器的引數下標

  上面是通過構造器引數的名稱,匹配需要傳入的值,那種方式最為直觀,而Spring還提供另外兩種方式匹配引數,這裡就來說說通過引數在引數列表中的下標進行匹配的方式。下面的配置,請結合2.2節中UserCar的構造方法一起閱讀,配置方式如下:

<bean id="car" class="cn.tewuyiang.pojo.Car">
    <!-- 下標編號從0開始,構造器的第一個引數是speed,為它賦值100 -->
    <constructor-arg index="0" value="100" />
    <!-- 構造器的第二個引數是price,為它賦值99999.9 -->
    <constructor-arg index="1" value="99999.9"/>
</bean>

<bean id="user" class="cn.tewuyiang.pojo.User">
    <!-- 與上面car的配置同理 -->
    <constructor-arg index="0" value="aaa" />
    <constructor-arg index="1" value="123" />
    <constructor-arg index="2" ref="car" />
</bean>

  上面就是通過引數的下標為構造器的引數賦值,需要注意的是,參實的下標從0開始。使用上面的方式配置,若賦值的型別與引數的型別不一致,將會在容器初始化bean的時候丟擲異常。如果bean存在多個引數數量一樣的構造器,Spring容器會自動找到型別匹配的那個進行呼叫。比如說,Car有如下兩個構造器,Spring容器將會呼叫第二個,因為上面的配置中,index = 1對應的valuedouble型別,與第二個構造器匹配,而第一個不匹配:

public Car(double price, int speed) {
    this.speed = speed;
    this.price = price;
}
// 將使用匹配這個構造器
public Car(int speed, double price) {
    this.speed = speed;
    this.price = price;
}

  還存在另外一種特殊情況,那就是多個構造器都滿足bean的配置,此時選擇哪一個?假設當前car的配置是這樣的:

<bean id="car" class="cn.tewuyiang.pojo.Car">
    <!-- 兩個下標的value值都是整數 -->
    <constructor-arg index="0" value="100" />
    <constructor-arg index="1" value="999"/>
</bean>

  假設Car還是有上面兩個構造器,兩個構造器都是一個int型別一個double型別的引數,只是位置不同。而配置中,指定的兩個值都是int型別。但是,int型別也可以使用double型別儲存,所以上面兩個構造器都是匹配的,此時呼叫哪一個呢?結論就是呼叫第二個。自己去嘗試就會發現,若存在多個構造器匹配bean的定義,Spring容器總是使用最後一個滿足條件的構造器


(三)匹配構造器的引數型別

  下面說最後一種匹配方式——匹配構造器的引數型別。直接看配置檔案吧:

<bean id="car" class="cn.tewuyiang.pojo.Car">
    <!-- 使用type屬性匹配型別,car的構造器包含兩個引數,一個是int型別,一個是double型別 -->
    <constructor-arg type="int" value="100" />
    <constructor-arg type="double" value="99999.9"/>
</bean>

<bean id="user" class="cn.tewuyiang.pojo.User">
    <!-- 對於引用型別,需要使用限定類名 -->
    <constructor-arg type="java.lang.String" value="aaa" />
    <constructor-arg type="int" value="123" />
    <constructor-arg type="cn.tewuyiang.pojo.Car" ref="car" />
</bean>

  上面應該不難理解,直接通過匹配構造器的引數型別,從而選擇一個能夠完全匹配的構造器,呼叫這個構造器完成bean的建立和屬性注入。需要注意的是,上面的配置中,型別並不需要按構造器中宣告的順序編寫,Spring也能進行匹配。這也就意味著可能出現多個能夠匹配的構造器,和上一個例子中一樣。比如說,Car還是有下面兩個構造器:

public Car(double price, int speed) {
    // 輸出一句話,看是否呼叫這個構造器
    System.out.println(111);
    this.speed = speed;
    this.price = price;
}
// 將使用匹配這個構造器
public Car(int speed, double price) {
    // 輸出一句話,看是否呼叫這個構造器
    System.out.println(222);
    this.speed = speed;
    this.price = price;
}

  上面兩個構造器都是一個int,一個double型別的引數,都符合xml檔案中,car這個bean的配置。通過測試發現,Spring容器使用的永遠都是最後一個符合條件的構造器,這和上面通過下標匹配是一致的。需要說明的一點是,這三種使用構造器注入的方式,可以混用


2.4 靜態工廠注入

  靜態工廠注入就是我們編寫一個靜態的工廠方法,這個工廠方法會返回一個我們需要的值,然後在配置檔案中,我們指定使用這個工廠方法建立bean。首先我們需要一個靜態工廠,如下所示:

public class SimpleFactory {

    /**
     * 靜態工廠,返回一個Car的例項物件
     */
    public static Car getCar() {
        return new Car(12345, 5.4321);
    }
}

  下面我們需要在xml中配置car這個bean,並指定它由工廠方法進行建立。配置如下:

<!-- 
	注意,這裡的配置並不是建立一個SimpleFactory物件,取名為myCar,
    這一句配置的意思是,呼叫SimpleFactory的getCar方法,建立一個car例項物件,
    將這個car物件取名為myCar。
-->
<bean id="car" class="cn.tewuyiang.factory.SimpleFactory" factory-method="getCar"/>

<bean id="user" class="cn.tewuyiang.pojo.User">
    <!-- name和age使用set注入 -->
    <property name="name" value="aaa"/>
    <property name="age" value="123"/>
    <!-- 將上面配置的car,注入到user的car屬性中 -->
    <property name="car" ref="car"/>
</bean>

  以上就配置成功了,測試方法以及執行效果如下,注意看car的屬性值,就是我們在靜態工廠中配置的那樣,這說明,Spring容器確實是使用我們定義的靜態工廠方法,建立了car這個bean

@Test
public void test1() {
    ApplicationContext context =
        new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    // 獲取靜態工廠建立的car
    Car car = (Car) context.getBean("car");
    // 獲取user
    User user = context.getBean(User.class);
    System.out.println(car);
    System.out.println(user);
}

  輸出如下所示:

Car{speed=12345, price=5.4321}
User{name='aaa', age=123, car=Car{speed=12345, price=5.4321}}

2.5 例項工廠注入

  例項工廠與靜態工廠類似,不同的是,靜態工廠呼叫工廠方法不需要先建立工廠類的物件,因為靜態方法可以直接通過類呼叫,所以在上面的配置檔案中,並沒有宣告工廠類的bean。但是,例項工廠,需要有一個例項物件,才能呼叫它的工廠方法。我們先看看例項工廠的定義:

public class SimpleFactory {

    /**
     * 例項工廠方法,返回一個Car的例項物件
     */
    public Car getCar() {
        return new Car(12345, 5.4321);
    }

    /**
     * 例項工廠方法,返回一個String
     */
    public String getName() {
        return "tewuyiang";
    }

    /**
     * 例項工廠方法,返回一個int,在Spring容器中會被包裝成Integer
     */
    public int getAge() {
        return 128;
    }
}

  在上面的工廠類中,共定義了三個工廠方法,分別用來返回user所需的carname以及age,而配置檔案如下:

<!-- 宣告例項工廠bean,Spring容器需要先建立一個SimpleFactory物件,才能呼叫工廠方法 -->
<bean id="factory" class="cn.tewuyiang.factory.SimpleFactory" />

<!-- 
    通過例項工廠的工廠方法,建立三個bean,通過factory-bean指定工廠物件,
    通過factory-method指定需要呼叫的工廠方法
-->
<bean id="name" factory-bean="factory" factory-method="getName" />
<bean id="age" factory-bean="factory" factory-method="getAge" />
<bean id="car" factory-bean="factory" factory-method="getCar" />

<bean id="user" class="cn.tewuyiang.pojo.User">
    <!-- 將上面通過例項工廠方法建立的bean,注入到user中 -->
    <property name="name" ref="name"/>
    <property name="age" ref="age"/>
    <property name="car" ref="car"/>
</bean>

  我們嘗試從Spring容器中取出nameagecar以及user,看看它們的值,測試程式碼如下:

@Test
public void test1() {
    ApplicationContext context =
        new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    // 獲取靜態工廠建立的car,name和age這三個bean
    Car car = (Car) context.getBean("car");
    String name = (String) context.getBean("name");
    Integer age = (Integer) context.getBean("age");
    // 獲取user這個bean
    User user = context.getBean(User.class);
    System.out.println(car);
    System.out.println(name);
    System.out.println(age);
    System.out.println(user);
}

  以下就是輸出結果,可以看到,我們通過工廠建立的bean,都在Spring容器中能夠獲取到:

Car{speed=12345, price=5.4321}
tewuyiang
128
User{name='tewuyiang', age=128, car=Car{speed=12345, price=5.4321}}

2.6 使用註解注入

  假如需要使用註解的方式為bean注入屬性值,應該這麼操作呢?首先,如果bean依賴於其他bean(比如User依賴Car),那麼我們可以使用@Autowired或者@Resource這兩個註解進行依賴注入,這個大家應該都知道。但是如果要為基本資料型別或者是Java的封裝型別(比如String)賦值呢?這時候可以使用@Value註解。這裡我就不演示了,感興趣的可以自行去研究,應該是比xml的方式簡單多了。


三、總結

  以上就對Spring基於xml配置檔案進行屬性注入的方式做了一個還算詳細的介紹。其實這一部分的內容還是比較基礎,畢竟只是Spring的使用,並不涉及原理,只要自己嘗試寫一遍就瞭解了。若以上內容存在錯誤或不足,歡迎指正,共同進步。也希望以上內容對需要的人有所幫助。


四、參考

相關文章