15.4 Spring控制反转容器的使用

15.4 Spring控制反转容器的使用

本节主要介绍**Spring如何管理bean依赖关系**。

15.4.1 通过构造器创建一个bean实例

前面已经介绍,通过调用ApplicationContextgetBean方法可以获取到一个bean的实例。下面的配置文件中定义了一个名为productbean

一个简单的配置文件

1
2
3
4
5
6
7
8
<?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">
<!-- 通过默认无参的构造器来除注入Product类 -->
<bean name="product" class="app15a.bean.Product"/>
</beans>

bean的定义告诉Spring通过默认无参的构造器来初始化Product。如果不存在该构造器(因为类作者重载了构造器,且没有显式定义默认构造器),则Spring将抛出一个异常。
注意**,应采用id或者name属性标识一个bean**。为了让Spring创建一个Product实例,应将bean定义的name值“product”(具体实践中也可以是id值)和Product类型作为参数传递给ApplicationContextgetBean方法:

1
2
3
4
5
6
ApplicationContext context =
new ClassPathXmlApplicationContext(
new String[] {"spring-config.xml"});
Product product1 = context.getBean("product", Product.class);
product1.setName("Excellent snake oil");
System.out.println("product1: " + product1.getName());

15.4.2 通过工厂方法创建一个bean实例

除了通过类的构造器方式,**Spring还同样支持通过调用一个工厂的方法来初始化类**。下面的bean定义展示了通过工厂方法来实例化java.util.Calendar

1
2
3
<!-- 使用id属性来标识一个bean -->
<bean id="calendar" class="java.util.Calendar"
factory-method="getInstance"/>

本例中采用了id属性,而非name属性来标识bean,并采用了getBean方法来获取Calendar实例:

1
2
3
4
5
6
7
// 加载定义bean的xml文件
ApplicationContext context =
new ClassPathXmlApplicationContext(
new String[] {"spring-config.xml"});
//获取实例
Calendar calendar =
context.getBean("calendar", Calendar.class);

15.4.3 DestroyMethod的使用

有时,我们希望一些类在被销毁前能执行一些方法。Spring考虑到了这样的需求。可以在bean定义中配置destroy-method属性,来指定在销毁前要被执行的方法
下面的例子中,我们配置Spring通过java.util.concurrent.Executors的静态方法newCachedThreadPool来创建一个java.uitl.concurrent.ExecutorService实例,并指定了destroy-method属性值为shutdown方法。这样,Spring会在销毁ExecutorService实例前调用其shutdown方法:

1
2
3
<bean id="executorService" class="java.util.concurrent.Executors"
factory-method="newCachedThreadPool"
destroy-method="shutdown"/>

15.4.4 向构造器传递参数

Spring支持通过带参数的构造器来初始化类。

Product类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package app15a.bean;
import java.io.Serializable;
public class Product
implements
Serializable
{
private static final long serialVersionUID = 748392348L;
private String name;
private String description;
private float price;
public Product()
{}
public Product(String name,String description,float price)
{
this.name = name;
this.description = description;
this.price = price;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public float getPrice()
{
return price;
}
public void setPrice(float price)
{
this.price = price;
}
}

如下定义展示了如何通过参数名传递参数:

1
2
3
4
5
6
7
8
9
10
11
<!-- 调用:public Product(String name,String description,float price) -->
<bean name="featuredProduct" class="app15a.bean.Product">
<!-- 构造器使用构造器注入 -->
<!-- 给构造器的name参数赋值 -->
<constructor-arg name="name" value="终极橄榄油" />
<!-- 给构造器的description参数赋值 -->
<constructor-arg name="description"
value="市场上最纯的橄榄油" />
<!-- 给构造器的price参数赋值 -->
<constructor-arg name="price" value="9.95" />
</bean>

这样,在创建Product实例时,Spring会调用如下构造器:

1
2
3
4
5
public Product(String name, String description, float price) {
this.name = name;
this.description = description;
this.price = price;
}

除了通过名称传递参数外,Spring还支持通过下标方式传递参数,具体如下:

1
2
3
4
5
6
7
<bean name="featuredProduct2" class="app15a.bean.Product">
<constructor-arg index="0"
value="终极橄榄油" />
<constructor-arg index="1"
value="市场上最纯的橄榄油" />
<constructor-arg index="2" value="9.95" />
</bean>

需要说明的是,采用这种下标方式,对应构造器的所有参数必须传递,缺一不可。

15.4.5 setter方式依赖注入

下面以Employee类和Address类为例,介绍setter方式依赖注入。

Employee类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package app15a.bean;
public class Employee
{
private String firstName;
private String lastName;
private Address homeAddress;
public Employee()
{}
public Employee(String firstName,String lastName,Address homeAddress)
{
this.firstName = firstName;
this.lastName = lastName;
this.homeAddress = homeAddress;
}
@Override
public String toString()
{
return firstName + lastName + "," + homeAddress;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName = firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName = lastName;
}
public Address getHomeAddress()
{
return homeAddress;
}
public void setHomeAddress(Address homeAddress)
{
this.homeAddress = homeAddress;
}
}

Address类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package app15a.bean;
public class Address
{
// 国家
private String country;
// 城市
private String city;
// 街道
private String state;
// 街道编号
private String stateId;
// 邮编
private String zipCode;
@Override
public String toString()
{
return country + "," + city + "," + state + "," + stateId + ","
+ zipCode;
}
public Address(String country,String city,String state,String stateId,
String zipCode)
{
this.country = country;
this.city = city;
this.state = state;
this.stateId = stateId;
this.zipCode = zipCode;
}
//setter和getter方法省略...
}

Employee依赖于Address类,可以通过如下配置来保证每个Employee实例都能包含Address实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 使用构造器方式注入依赖 -->
<bean name="simpleAddress" class="app15a.bean.Address">
<constructor-arg name="country" value="中国" />
<constructor-arg name="city" value="北京" />
<constructor-arg name="state" value="雾霾大道" />
<constructor-arg name="stateId"
value="996" />
<constructor-arg name="zipCode" value="1314" />
</bean>
<!-- 使用setter方法来给bean注入值 -->
<bean name="employee1" class="app15a.bean.Employee">
<property name="firstName" value="张" />
<property name="lastName" value="三" />
<!-- 引用另一个bean -->
<property name="homeAddress" ref="simpleAddress" />
</bean>

simpleAddress对象是Address类的一个实例,其通过构造器方式实例化。employee1对象则通过配置property元素来调用setter方法以设置值。需要注意的是,**homeAddress属性配置的是simpleAddress对象的引用**。
被引用对象A的配置定义不需要在引用该对象A的对象B之前定义。本例中,employee1对象可以出现在simpleAddress对象定义之前。

15.4.6 构造器方式依赖注入

上面的Employee类提供了一个可以传递参数的构造器,如下所示:

1
2
3
4
5
6
7
//可以通过构造器注入
public Employee(String firstName, String lastName,
Address homeAddress) {
this.firstName = firstName;
this.lastName = lastName;
this.homeAddress = homeAddress;
}

所以,我们还可以将Address对象通过构造器注入,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 使用构造器方法注入依赖 -->
<bean name="employee2" class="app15a.bean.Employee">
<constructor-arg name="firstName" value="李" />
<constructor-arg name="lastName" value="四" />
<!-- 引用另一个beans -->
<constructor-arg name="homeAddress"
ref="simpleAddress" />
</bean>
<!-- 使用构造器方式注入依赖 -->
<bean name="simpleAddress" class="app15a.bean.Address">
<constructor-arg name="country" value="中国" />
<constructor-arg name="city" value="北京" />
<constructor-arg name="state" value="雾霾大道" />
<constructor-arg name="stateId"
value="996" />
<constructor-arg name="zipCode" value="1314" />
</bean>

完整代码

项目结构

spring-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?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">
<!-- 定义一个name为product,类型为app15a.bean.Product的bean -->
<bean name="product" class="app15a.bean.Product" />
<bean name="featuredProduct" class="app15a.bean.Product">
<!-- public Product(String name,String description,float price) -->
<!-- 构造器使用构造器注入 -->
<!-- 给构造器的name参数赋值 -->
<constructor-arg name="name" value="终极橄榄油" />
<!-- 给构造器的description参数赋值 -->
<constructor-arg name="description"
value="市场上最纯的橄榄油" />
<!-- 给构造器的price参数赋值 -->
<constructor-arg name="price" value="9.95" />
</bean>
<bean name="featuredProduct2" class="app15a.bean.Product">
<constructor-arg index="0"
value="终极橄榄油" />
<constructor-arg index="1"
value="市场上最纯的橄榄油" />
<constructor-arg index="2" value="9.95" />
</bean>
<!-- 使用工厂方法来生成bean -->
<bean id="calendar" class="java.util.Calendar"
factory-method="getInstance" />
<!-- 使用setter方法来给bean注入值 -->
<bean name="employee1" class="app15a.bean.Employee">
<property name="firstName" value="张" />
<property name="lastName" value="三" />
<!-- 引用另一个bean -->
<property name="homeAddress" ref="simpleAddress" />
</bean>
<!-- 使用构造器方法注入依赖 -->
<bean name="employee2" class="app15a.bean.Employee">
<constructor-arg name="firstName" value="李" />
<constructor-arg name="lastName" value="四" />
<!-- 引用另一个beans -->
<constructor-arg name="homeAddress"
ref="simpleAddress" />
</bean>
<!-- 使用构造器方式注入依赖 -->
<bean name="simpleAddress" class="app15a.bean.Address">
<constructor-arg name="country" value="中国" />
<constructor-arg name="city" value="北京" />
<constructor-arg name="state" value="雾霾大道" />
<constructor-arg name="stateId"
value="996" />
<constructor-arg name="zipCode" value="1314" />
</bean>
</beans>

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package app15a;
import java.util.Calendar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import app15a.bean.Employee;
import app15a.bean.Product;
public class Main
{
public static void main(String[] args)
{
@SuppressWarnings(
"resource"
)
// 从类的加载路径中,加载xml文件之中的beans
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]
{"spring-config.xml"});
// 获取xml中定义的的bean
// 将获得spring-config.xml中定义的bean:
// <bean name="product" class="app15a.bean.Product" />
Product product1 = context.getBean("product", Product.class);
// 调用这个对象的setName方法
product1.setName("真是个棒棒的产品");
System.out.println("product1: " + product1.getName());
// 还是获取name为product的bean,
// 这和第一次获取到的bean是同一bean,因为name属性一样
// 所以这里可以不用设置值就可以输出
Product product2 = context.getBean("product", Product.class);
// 因为是同一bean,所以打印内容一样
System.out.println("product2: " + product2.getName());
// 获取name属性为:featuredProduct的bean:
Product featuredProduct = context.getBean("featuredProduct",
Product.class);
// 因为已经在xml文件之中通过构造器注入值了,这里可以直接取出
System.out.println("featuredProduct: " + featuredProduct.getName()
+ ", " + featuredProduct.getDescription() + ", "
+ featuredProduct.getPrice());
// 获取id为calendar的bean
Calendar calendar = context.getBean("calendar",
java.util.Calendar.class);
// 调用其方法
System.out.println("calendar: " + calendar.getTime());
// 获取id为employee1的bean
Employee employee1 = context.getBean("employee1", Employee.class);
System.out.print(
"employee1: " + employee1.getFirstName() + employee1.getLastName());
System.out.println(",地址:" + employee1.getHomeAddress());
// 获取id或者name为employee2的bean
Employee employee2 = context.getBean("employee2", Employee.class);
System.out.print(
"employee2: " + employee2.getFirstName() + employee2.getLastName());
System.out.println(",地址:" + employee2.getHomeAddress());
}
}