一、前言
最近在複習Spring
的相關內容,這篇部落格就來記錄一下Spring
為bean
的屬性注入值的四種方式。這篇部落格主要講解在xml
檔案中,如何為bean
的屬性注入值,最後也會簡單提一下使用註解的方式。廢話不多說,直接開始吧。
二、正文
2.1 注入方式
在Spring
中,共有四種方式為bean
的屬性注入值,分別是:
- set方法注入
- 構造器注入
- 靜態工廠注入
- 例項工廠注入
下面我就分別演示一下,如何使用這四種方式進行屬性的注入。
2.2 set方法注入
在演示前,我們需要準備幾個類,我使用下面兩個類來進行注入的演示,這兩個類分別是User
和Car
類:
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
容器就是通過呼叫bean
的set
方法為屬性注入值的。而在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>
通過上面的配置,就可以為Car
和User
這兩個型別的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
中定義的Car
和User
這兩個類,測試方法以及類的定義都不需要變,需要改變的僅僅是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
節中User
和Car
的構造方法一起閱讀,配置方式如下:
<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
對應的value
是double
型別,與第二個構造器匹配,而第一個不匹配:
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
所需的car
,name
以及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
容器中取出name
,age
,car
以及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
的使用,並不涉及原理,只要自己嘗試寫一遍就瞭解了。若以上內容存在錯誤或不足,歡迎指正,共同進步。也希望以上內容對需要的人有所幫助。