7.11.2 使用c:命名空间简化配置

p:命名空间主要用于简化设值注入,而c:命名空间则用于简化构造注入。

假设有如下的持久化类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.crazyit.app.service.impl;

import org.crazyit.app.service.*;

public class Chinese implements Person
{
private Axe axe;
private int age;
// 构造注入所需的带参数的构造器
public Chinese(Axe axe, int age)
{
this.axe = axe;
this.age = age;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
// 调用axe的chop()方法
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
System.out.println("age成员变量的值:" + age);
}
}

上面Chinese类的构造器需要两个参数,传统配置是在<bean>元素中添加两个<constructor-arg>子元素来代表构造器参数;导入c:命名空间之后,可以直接使用属性来配置构造器参数

c:属性的使用格式

使用c:指定构造器参数的格式为:

  • c:构造器参数名="值"
  • c:构造器参数名-ref=其他Bean的id"

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\c_namespace
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ ├─SteelAxe.java
│ └─StoneAxe.java
└─Person.java

beans.xml

本应用的配置文件beans.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置chinese实例,其实现类是Chinese -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
c:axe-ref="steelAxe" c:age="29"/>
<!-- 配置chinese实例,其实现类是Chinese -->
<bean id="chinese2" class="org.crazyit.app.service.impl.Chinese"
c:_0-ref="steelAxe" c:_1="29"/>
<!-- 配置stoneAxe实例,其实现类是StoneAxe -->
<bean id="stoneAxe" class="org.crazyit.app.service.impl.StoneAxe"/>
<!-- 配置steelAxe实例,其实现类是SteelAxe -->
<bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe"/>
</beans>

引入c:名字空间

配置文件中<beans>元素上的xmlns:c属性:

1
xmlns:c="http://www.springframework.org/schema/c"

用于导入XML Schema里的c:命名空间

使用 c:构造器参数名 来设置构造器参数

配置文件的如下代码

1
2
3
<!-- 配置chinese实例,其实现类是Chinese -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
c:axe-ref="steelAxe" c:age="29"/>

则直接使用属性配置了axeage两个构造器参数,由于axe构造器参数需要引用容器中另一个已存在的Bean实例,故在axe后增加了"ref"后缀,这个后缀指定该值不是一个具体的值,而是对另外个Bean的引用。

使用 c:_索引 来设置构造器参数

上面配置方式是在c:后使用构造器参数名来指定构造器参数, Spring还支持一种通过索引来配置构造器参数的方式。上面的Bean也可改写为如下形式:

1
2
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
c:_0-ref="steelAxe" c:_1="29/>

c:_0-ref属性指定使用容器中已有的idsteelAxe这个Bean作为第一个构造器参数,
c:_1="29"则指定使用29作为第二个构造器参数。

在这种方式下,c:_N中的N代表第几个构造器参数(N从0开始)。

使用constructor-arg元素要按构造器参数的顺序注入

前面介绍介绍构造注入时,通常总是根据构造参数的顺序来注入,比如说希望调用Person类的构造器Person(String,int),则在XML中配置时需要将String构造参数对应的<constructor-arg>元素放在第1位,将int构造参数对应的<constructor-arg>元素放在第2位

如何根据构造器参数的名称来注入

如果希望根据构造参数的名称来配置构造注入,则可使用java.beans包的@ConstructorProperties注解

程序示例

1
2
3
4
5
6
7
8
9
10
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\ConstructorProperties
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
└─Person.java

例如如下代码使用@ConstructorProperties注解为构造参数指定参数名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.crazyit.app.service;
import java.beans.ConstructorProperties;
public class Person
{
private String name;
private int age;
@ConstructorProperties({"personName", "age"})
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "Person[name=" + name + ",age=" + this.age + "]";
}
}

上面程序中的@ConstructorProperties({"personName", "age"})注解,指定Person构造器的两个构造参数的名字为:personNameage(@ConstructorProperties注解指定的名字并不需要与构造参数实际的名字相同)。
接下来就可在配置文件beans.xml中通过构造参数名来执行构造注入的配置了,例如如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用ConstructorProperties配置了构造参数名之后,
接下来即可通过构造参数名来配置构造注入 -->
<bean id="person" class="org.crazyit.app.service.Person">
<constructor-arg name="age" value="500" />
<constructor-arg name="personName" value="孙悟空" />
</bean>
</beans>

当然也可使用c:命名空间进行简化配置,上面配置可改为如下形式。

1
2
<bean id="person" class="org.crazyit.app.service.Person"
c:age="500" c:personName="孙悟空" />

7.11.1 使用p:命名空间简化配置

p:命名空间甚至不需要特定的Schema定义,它直接存在于Spring内核中。与前面用<property>元素定义Bean的属性不同的是,当导入p:命名空间之后,就可直接在<bean>元素中使用属性来驱动执行setter方法

程序示例

假设有如下的持久化类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\p_namespace
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ ├─SteelAxe.java
│ └─StoneAxe.java
└─Person.java

Chinese.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
package org.crazyit.app.service.impl;

import org.crazyit.app.service.*;

public class Chinese implements Person
{
private Axe axe;
private int age;
public Chinese()
{
}
// axe的setter方法
public void setAxe(Axe axe)
{
this.axe = axe;
}
// age的setter方法
public void setAge(int age)
{
this.age = age;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
System.out.println(axe.chop());
System.out.println("age成员变量的值:" + age);
}
}

上面的持久化类中有setAxe()setAge()两个setter方法可通过设值注入来驱动,如果采用原来的配置方式,则需要使用<property>元素来驱动它们。但如果采用p:命名空间,则可直接采用属性来配置它们。本应用的配置文件如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素和Schema 并导入p:命名空间的元素 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置chinese实例,其实现类是Chinese -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
p:age="29" p:axe-ref="stoneAxe" />
<!-- 配置steelAxe实例,其实现类是SteelAxe -->
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<!-- 配置stoneAxee实例,其实现类是StoneAxe -->
<bean id="stoneAxe"
class="org.crazyit.app.service.impl.StoneAxe" />
</beans>

导入p:命名空间

配置文件中<beans>元素中的xmlns:p属性:

1
xmlns:p="http://www.springframework.org/schema/p"

用于导入XML Schema里的p:命名空间

使用p:属性名进行设值注入

在bean元素中加入p:属性名="属性值"这个格式的属性即可直接对属性执行设值注入。如果在属性名后增加了"-ref"后缀,则指定该值不是一个具体的值,而是对另外一个Bean的引用。

使用p:命名空间时Bean内的属性名不要以-ref结尾

使用p:命名空间没有标准的XML格式灵活,如果某个Bean的属性名是以-ref结尾的,那么采用p:命名空间定义时就会发生冲突,而采用标准的XML格式定义则不会出现这种问题。

7.11 基于XML Schema的简化配置方式

Spring2.0开始, Spring允许使用基于XML Schema的配置方式来简化Spring配置文件。
早期Spring<bean>元素即可配置所有的Bean实例,而每个设值注入再用一个<property>元素即可。这种配置方式简单、直观,而且能以相同风格处理所有Bean的配置——唯一的缺点是配置烦琐,当Bean实例的属性足够多,且属性类型复杂(大多是集合属性)时,基于DTD的配置文件将变得更加烦琐。
在这种情况下, Spring提出了使用基于XML Schema的配置方式。这种配置方式更加简洁,可以对Spring配置文件进行”减肥”,但需要花一些时间来了解这种配置方式。

7.10.3 获取方法返回值

通过MethodInvokingFactoryBean这个工厂Bean,可调用任意类的类方法,也可调用任意对象的实例方法,如果调用的方法有返回值,则既可将该指定方法的返回值定义成容器中的Bean,也可将指定方法的返回值注入给其他Bean
使用MethodInvokingFactoryBean来调用任意方法时,可分为两种情形。

调用静态方法

如果希望调用的方法是静态方法,则需要指定:

  1. 调用哪个类。通过MethodInvokingFactoryBeansetTargetClass(String targetClass)方法指定。
  2. 调用哪个方法。通过MethodInvokingFactoryBeansetMethod(String targetMethod)方法指定。
  3. 调用方法的参数。通过MethodInvokingFactoryBeansetArguments(Object[] arguments)方法指定。如果希望调用的方法无须参数,则可以省略该配置。

调用实例方法

如果希望调用的方法是实例方法,则需要指定:

  1. 调用哪个对象。通过MethodInvokingFactoryBeansetTargetobject(Object targetObject)方法指定。
  2. 调用哪个方法。通过MethodInvokingFactoryBeansetTargetMethod(String targetMethod)方法指定。
  3. 调用方法的参数。通过MethodInvokingFactoryBeansetArguments(Object[] arguments)方法指定。如果希望调用的方法无须参数,则可以省略该配置。

假设有如下一段Java代码:

1
2
3
4
5
6
7
8
9
10
11
JFrame win=new JFrame("我的窗口");
JTextArea jta=JTextArea(7,40);
win.add(new JScrollPane(jta));
JPanel jp=new JPanel();
win.add(jp,BorderLayout.SOUTH)
JButton jb1=new JButton("确定");
jp.add(jb1);
JButton jp2=new JButton("取消");
jp.add(jb2);
win.pack();
win.setVisible(true);

这段代码是一段很”随意”的Java代码(可以是任意一段Java代码),别人给你任何一段Java代码,你都应该能用Spring配置文件将它配置出来。

程序示例

1
2
3
4
5
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\MethodInvokingFactoryBean
└─src\
├─beans.xml
└─lee\
└─SpringTest.java

下面使用XML配置文件将上面这段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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 下面配置相当于如下Java代码:
JFrame win = new JFrame("我的窗口");
win.setVisible(true); -->
<bean id="win" class="javax.swing.JFrame">
<constructor-arg value="我的窗口" type="java.lang.String"/>
<property name="visible" value="true"/>
</bean>

<!-- 下面配置相当于如下Java代码:
JTextArea jta = JTextArea(7, 40); -->
<bean id="jta" class="javax.swing.JTextArea">
<constructor-arg value="7" type="int"/>
<constructor-arg value="40" type="int"/>
</bean>

<!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法
下面配置相当于如下Java代码:
win.add(new JScrollPane(jta)); -->
<bean class=
"org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="win"/>
<property name="targetMethod" value="add"/>
<property name="arguments">
<list>
<bean class="javax.swing.JScrollPane">
<constructor-arg ref="jta"/>
</bean>
</list>
</property>
</bean>

<!-- 下面配置相当于如下Java代码:
JPanel jp = new JPanel(); -->
<bean id="jp" class="javax.swing.JPanel"/>

<!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法
下面配置相当于如下Java代码:
win.add(jp , BorderLayout.SOUTH); -->
<bean class=
"org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="win"/>
<property name="targetMethod" value="add"/>
<property name="arguments">
<list>
<ref bean="jp"/>
<util:constant static-field="java.awt.BorderLayout.SOUTH"/>
</list>
</property>
</bean>

<!-- 下面配置相当于如下Java代码:
JButton jb1 = new JButton("确定"); -->
<bean id="jb1" class="javax.swing.JButton">
<constructor-arg value="确定" type="java.lang.String"/>
</bean>

<!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法
下面配置相当于如下Java代码:
jp.add(jb1); -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="jp"/>
<property name="targetMethod" value="add"/>
<property name="arguments">
<list>
<ref bean="jb1"/>
</list>
</property>
</bean>

<!-- 下面配置相当于如下Java代码:
JButton jb2 = new JButton("取消"); -->
<bean id="jb2" class="javax.swing.JButton">
<constructor-arg value="取消" type="java.lang.String"/>
</bean>

<!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法
下面配置相当于如下Java代码:
jp.add(jb2); -->
<bean class=
"org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="jp"/>
<property name="targetMethod" value="add"/>
<property name="arguments">
<list>
<ref bean="jb2"/>
</list>
</property>
</bean>
<!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法
下面配置相当于如下Java代码:
win.pack(); -->
<bean class=
"org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="win"/>
<property name="targetMethod" value="pack"/>
</bean>
</beans>

该示例的主程序非常简单,主程序只是简单的行:用于创建Spring容器。编译、运行该程序,可以看到如图7.4所示的界面。
通过上面示例证实了一点:几乎所有的Java代码都可以通过Spring XML配置文件配置出来连上面的Swing编程都可使用Spring XML配置文件来驱动。需要说明的是,此处只是向读者示范如何使用Spring配置文件来驱动执行任意一段Java代码,并非要大家使用Spring XML配置文件进行Swing界面编程.
经过上面的介绍不难发现: Spring框架的本质其实就是通过XML配置来执行Java代码,因此几乎可以把所有的Java代码放到Spring配置文件中管理。归纳一下:

  1. 调用构造器创建对象(包括使用工厂方法创建对象),用<bean>元素。
  2. 调用setter方法,用<property>元素。
  3. 调用getter方法,用PropertyPathFactoryBean<util:property-path>元素。
  4. 获取Field的值,用FieldRetrievingFactoryBean<util:constant>元素。
  5. 调用普通方法,用MethodInvokingFactoryBean工厂Bean

那么是否有必要把所有的Java代码都放在Sprin配置文件中管理呢?答案是否定的。过度使用XML配置文件不仅使得配置文件更加臃肿,难以维护,而且导致程序的可读性严重降低。
一般来说,应该将如下两类信息放到XML配置文件中管理。

  1. 项目升级、维护时经常需要改动的信息
  2. 控制项目内各组件耦合关系的代码。

这样就体现了Spring IoC容器的作用:将原来使用Java代码管理的耦合关系,提取到XML中进行管理,从而降低了各组件之间的耦合,提高了软件系统的可维护性

7.10.2 获取Field值

通过FieldRetrievingFactoryBean类,可访问类的静态Field值或对象的实例FieldFieldRetrieving Factory Bean获得指定Field的值之后,即可将获取的值注入其他Bean,也可直接定义成新的Bean
使用FieldRetrievingFactoryBean访问Field值可分为两种情形:
如果要访问的Field是静态Field,则需要指定:

  1. 调用哪个类。由FieldRetrievingFactoryBeansetTarget Class( String targetClass方法指定。
  2. 访问哪个Field。由FieldRetrievingFactoryBeansetTargetField(String target Field)方法指定。

如果要访问的Field是实例Field,则需要指定:

  1. 调用哪个对象。由FieldRetrievingFactoryBeansetTargetObject(Object targetObject)方法指定。
  2. 访问哪个Field。由FieldRetrievingFactoryBeansetTargetField(String targetField)方法指定

常用的是访问静态的Field

对于FieldRetrievingFactoryBean的第一种用法,与前面介绍FactoryBean时自己开发的GetFieldFactoryBean基本相同。对于FieldRetrievingFactoryBean的第二种用法,在实际编程中几乎没多大用处,这是因为FieldRetrievingFactoryBean则要求实例的Field要以public修饰,但是根据良好封装原则,Java类的实例Field应该用private修饰,并使用gettersetter来访问和修改,这与FieldRetrievingFactoryBean的要求矛盾。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\FieldRetrievingFactoryBean
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Person.java
└─Son.java

下面配置用于将指定类的静态Field定义成容器中的Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">

<!-- 将指定类的静态Field值定义成容器中的Bean实例-->
<bean id="theAge1" class=
"org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<!-- targetClass指定访问哪个目标类 -->
<property name="targetClass" value="java.sql.Connection"/>
<!-- targetField指定要访问的Field名 -->
<property name="targetField" value="TRANSACTION_SERIALIZABLE"/>
</bean>
</beans>

上面的XML配置粗体字代码指定访问java.sql.ConnectionTRANSACTION_SERIALIZABLE的值,并将该Fied的值定义成容器中的the Agel bean查阅JDK API文档即可发现该Field的值为8因此theAgel的值就是8。
主程序部分访问theAge1的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package lee;

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

public class SpringTest
{
public static void main(String[] args)
{
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
System.out.println("系统获取theAge1的值:" + ctx.getBean("theAge1"));
}
}

编译、运行该程序,将可看到如下输出:

1
系统获取theAge1的值:8

FieldRetrievingFactoryBean还提供了一个setStaticField(String staticField)方法,该方法可同时指定获取哪个类的哪个静态Field值。因此上面的配置片段可简化为如下形式:

1
2
3
4
5
6
<bean id="theAge2"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<!-- staticField指定访问哪个类的哪个静态Field -->
<property name="staticField"
value="java.sql.Connection.TRANSACTION_SERIALIZABLE" />
</bean>

使用FieldRetrievingFactory Bean获取的Field值既可定义成容器中的Bean,也可被注入到其他Bean中。例如如下配置。

1
2
3
4
5
6
7
<bean id="son" class="org.crazyit.app.service.Son">
<property name="age">
<!-- 指定java.sql.Connection.TRANSACTION_SERIALIZABLE 作为调用setAge()方法的参数值 -->
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>

主程序使用如下代码来访问、输出容器中的son:

1
System.out.println("系统获取son为:" + ctx.getBean("son"));

程序的执行结果如下:

1
系统获取son为:Son[age= 8]

从程序输出可以看出,sonage成员变量的值,等于java.sql.Connection接口中TRANSACTIONSERIALIZABLE的值。在上面定义中,定义FieldRetrievingFactoryBean工厂Bean时指定的id属性,并不是该工厂Bean实例的唯一标识,而是用来指定Field的表达式

util:constant元素

util:命名空间下的<util:constant>元素可作为FieldRetrievingFactoryBean访问静态Field的简化配置,使用该元素时可指定如下两个属性。

属性 描述
id 该属性指定将静态Field的值定义成名为idBean实例。
static-field 该属性指定访问哪个类的哪个静态Field

上面的theAge1theAge2可简化为如下配置:

1
2
<util:constant id="theAge1"
static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE" />

上面的son Bean可简化为如下配置:

1
2
3
4
5
6
<bean id="son" class="org.crazyit.app.service.Son">
<property name="age">
<util:constant
static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE" />
</property>
</bean>

7.10.1 获取其他Bean的属性值

PropertyPathFactoryBean用来获取目标Bean的属性值(实际上就是它的getter方法的返回值),获得的值可注入给其他Bean,也可直接定义成新的Bean
使用PropertyPathFactoryBean来调用其他Beangetter方法需要指定如下信息。

  • 调用哪个对象。由PropertyPathFactoryBeansetTargetObject(Object targetObject)方法指定
  • 调用哪个getter方法。由PropertyPathFactoryBeansetPropertyPath(String propertyPath)方法指定。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\PropertyPathFactoryBean
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Person.java
└─Son.java

beans.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
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!--下面配置定义一个将要被引用的目标bean -->
<bean id="person" class="org.crazyit.app.service.Person">
<!-- 为setAge()方法指定参数值 -->
<property name="age" value="30" />
<property name="son">
<!-- 使用嵌套Bean为setSon()方法指定参数值 -->
<bean class="org.crazyit.app.service.Son">
<property name="age" value="11" />
</bean>
</property>
</bean>
<!-- 将指定Bean实例的getter方法返回值定义成son1 Bean -->
<bean id="son1"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<!-- 指定来自哪个Bean -->
<property name="targetBeanName" value="person" />
<!-- 指定来自哪个getter方法,son代表getSon() -->
<property name="propertyPath" value="son" />
<!-- 这两个属性组合起来就是`son1`这个Bean来自person这个bean的getSon()方法 -->
</bean>
<beans>

SpringTest.java

主程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package lee;

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

public class SpringTest
{
public static void main(String[] args)
{
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
System.out.println("系统获取的son1:" + ctx.getBean("son1"));
}
}

执行结果如下:

1
系统获取的son1:Son[age=11]

上面配置文件使用PropertyPathFactoryBean来获取指定Bean的、指定getter方法的返回值,如下代码:

1
2
3
4
5
6
7
8
9
<!-- 将指定Bean实例的getter方法返回值定义成son1 Bean -->
<bean id="son1"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<!-- 指定来自哪个Bean -->
<property name="targetBeanName" value="person" />
<!-- 指定来自哪个getter方法,son代表getSon() -->
<property name="propertyPath" value="son" />
<!-- 这两个属性组合起来就是`son1`这个Bean来自person这个bean的getSon()方法 -->
</bean>

指定了获取person这个BeangerSon()方法的返回值,然后将返回值直接定义成容器中idson1Bean.

PropertyPathFactoryBean就是工厂Bean,关于工厂Bean的介绍可参考7.8.3节的内容,工厂Bean专门返回某个类型的值,并不是返回该Bean的实例。

PropertyPathFactoryBean的工厂Bean的id属性是属性表达式的值

注意:配置PropertyPathFactoryBean工厂Bean时指定的id属性,并不是该工厂Bean的唯一标识,而是用于指定属性表达式的值,可以对该属性表达式的值进行简写,例如:将该工厂Beanid属性设置为person.son.age则表示person.getSon().getAge()的返回值作为一个Bean.

1
2
3
<!-- 以下是访问指定Bean的getter方法的简单方式, person.son.age代表获取person.getSon().getAge() -->
<bean id="person.son.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />

Spring获取指定Beangetter方法的返回值之后,该返回值不仅可直接定义成容器中的Bean实例,还可注入另一个Bean作为成员变量
对上面的配置文件增加如下一段。

1
2
3
4
5
6
7
8
9
<!-- 下面定义son2 Bean -->
<bean id="son2" class="org.crazyit.app.service.Son">
<property name="age">
<!-- 使用嵌套Bean为调用setAge()方法指定参数值 -->
<!-- 以下是访问指定Bean的getter方法的简单方式, person.son.age代表获取person.getSon().getAge() -->
<bean id="person.son.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
</property>
</bean>

主程序部分增加如下的输出语句:

1
System.out.println("系统获取的son2:" + ctx.getBean("son2"));

运行主程序,此输出语句的执行结果如下:

1
系统获取的son2:Son[age=11]

从上面的粗体字代码可以看出,程序调用son2实例的setAge()方法时的参数并不是直接指定的,而是将容器中另一个Bean实例的属性值( getter方法的返回值)作为setAge()方法的参数,PropertyPathFactoryBean工厂Bean负责获取容器中另一个Bean的属性值(getter方法的返回值)

使用复合形式设置getter方法

PropertyPathFactoryBeansetPropertyPath()方法指定属性表达式时,还支持使用复合属性的形式,例如:想获取person BeangetSon().getAge()的返回值,可采用son.age的形式.

在配置文件中再增加如下一段:

1
2
3
4
5
6
7
8
<!-- 将基本数据类型的属性值定义成Bean实例 -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<!-- 确定目标Bean-->
<property name="targetBeanName" value="person" />
<!-- 使用复合属性来指定getter方法。son.age代表getSon().getAge() -->
<property name="propertyPath" value="son.age" />
</bean>

主程序部分增加如下输出语句:

1
System.out.println("系统获取的theAge的值:" + ctx.getBean("theAge"));

该条语句输出结果如下:

1
系统获取的theAge的值:11

以嵌套的Bean实例作为目标Bean

目标Bean既可以是容器中已有的Bean实例,也可以是嵌套Bean实例。因此,下面的定义也是有效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 将基本数据类型的属性值定义成Bean实例 -->
<bean id="theAge2"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<!-- 确定目标Bean,此处采用嵌套Bean定义目标Bean -->
<property name="targetObject">
<!-- 目标Bean是嵌套Bean -->
<bean class="org.crazyit.app.service.Person">
<property name="age" value="30" />
</bean>
</property>
<!-- 确定getter方法,age代表getAge() -->
<property name="propertyPath" value="age" />
</bean>

util:property-path元素

<util:property-path>元素可作为PropertyPathFactoryBean的简化配置,使用该元素时可指定如下两个属性。

属性 描述
id 该属性指定将getr方法的返回值定义成名为i的Bean实例。
path 该属性指定将哪个Bean实例、哪个属性(支持复合属性)暴露出来

如果需要使用<util:property-path>元素,则必须在Spring配置文件中导入util:命名空间。关于导入util:命名空间的详细步骤请参考7.11.3节。
上面的son1这个Bean

1
2
3
4
5
<bean id="son1"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetBeanName" value="person" />
<property name="propertyPath" value="son" />
</bean>

可简化为如下配置:

1
<util:property-path id="son1" path="person.son"/>

上面的son2这个Bean:

1
2
3
4
5
6
<bean id="son2" class="org.crazyit.app.service.Son">
<property name="age">
<bean id="person.son.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
</property>
</bean>

可简化为如下配置:

1
2
3
4
5
<bean id="son2" class="org.crazyit.app.service.Son">
<property name="age">
<util:property-path path="person.son.age" />
</property>
</bean>

7.10 高级依赖关系配置

Spring允许将Bean实例的所有成员变量,甚至基本类型的成员变量都通过配置文件来指定值,这种方式提供了很好的解耦。但是否真的值得呢?如果将基本类型的成员变量值也通过配置文件指定,虽然提供了很好的解耦,但大大降低了程序的可读性(必须同时参照配置文件才可知道程序中各成员变量的值)。因此,滥用依赖注入也会引起一些问题.
通常的建议是,组件与组件之间的耦合,采用依赖注入管理;但基本类型的成员变量值,应直接在代码中设置。对于组件之间的耦合关系,通过使用控制反转,代码变得非常清晰。因此,Bean无须管理依赖关系,而是由容器提供注入,Bean无须知道这些实例在哪里,以及它们具体的实现。
前面介绍的依赖关系,要么是基本类型的值,要么直接依赖于其他Bean。在实际的应用中,某个Bean实例的属性值可能是某个方法的返回值,或者类的Field值,或者另一个对象的getter方法返回值,Spring同样可以支持这种非常规的注入方式。 Spring甚至支持将任意方法的返回值、类或对象的Fed值、其他Beangetter法返回值,直接定义成容器中的一个Bean。下面将深入介绍这些特殊的注入形式。
Spng框架的本质是,开发者在Spring配置文件中使用XML元素进行配置,实际Spring执行相应的代码。例如:

  • 使用<bean>元素,实际启动Spring执行无参数或有参数的构造器,或者调用工厂方法创建Bean.
  • 使用<property>元素,实际驱动Spring执行一次setter方法。

Java程序还可能有其他类型的语句,如调用getter方法、调用普通方法、访问类或对象的Field,而Spring也为这种语句提供了对应的配置语法

  • 调用getter方法:使用PropertyPathFactoryBean
  • 访问类或对象的Field值:使用FieldRetrievingFactoryBean
  • 调用普通方法:使用MethodInvokingFactoryBean

7.9.3 协调作用域不同步的Bean

当两个singleton作用域的Bean存在依赖关系时,或者当prototype作用域的Bean依赖singleton作用域的Bean时,使用Spring提供的依赖注入进行管理即可。
singleton作用域的Bean只有一次初始化的机会,它的依赖关系也只在初始化阶段被设置,当singleton作用域的Bean依赖prototype作用域的Bean时, Spring容器会在初始化singleton作用域的Bean之前,先创建被依赖的prototype Bean,然后才初始化singleton Bean,并将prototype Bean注入singletonBean,这会导致以后无论何时通过singleton Bean去访问prototype Bean时,得到的永远是最初那个prototype Bean。这样就相当于**singleton bean把它所依赖的prototype Bean变成了singleton行为**。
假如有如图7.13所示的依赖关系。
这里有一张图片
对于图7.13所示的依赖关系,当Spring容器初始化时,容器会预初始化容器中所有的singleton Bean,由于singleton Bean依赖于prototype Bean,因此Spring在初始化singleton bean之前,会先创建prototype bean—然后才创建singleton Bean,接下来将prototype Bean注入singleton Bean。一旦singleton Bean初始化完成,它就持有了一个prototype Bean,容器再也不会为singleton bean执行注入了。
由于singleton Bean具有单例行为,当客户端多次请求singleton Bean时, Spring返回给客户端的将是同一个singleton bean实例,这不存在任何问题。

问题是:如果客户端通过该singleton Bean去调用prototype Bean的方法时—始终都是调用同一个prototype Bean实例,这就违背了设置prototype Bean的初衷——本来希望它具有prototype行为,但实际上它却表现出singleton行为。

如何解决singleton作用域依赖prototype作用域时的不同步现象

问题产生了:当singleton作用域的Bean依赖于prototype作用域的Bean时,会产生不同步的现象。解决该问题有如下两种思路。

  1. 放弃依赖注入:singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求新的Bean实例,即可保证每次注入的prototype Bean实例都是最新的实例
  2. 利用方法注入

推荐使用方法注入

第一种方式显然不是一个好的做法,代码主动请求新的Bean实例,必然导致程序代码与Spring API耦合,造成代码污染。
在通常情况下,建议使用方法注入

方法注入

方法注入通常使用lookup方法注入,使用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean(尽管也可以是一个singleton的)。 Spring通过使用JDK动态代理cglib库修改客户端的二进制码,从而实现上述要求。
假设程序中有一个Chinese类型的Bean,该Bean包含一个hunt方法,执行该方法时需要依赖于Dog的方法—而且程序希望每次执行hunt方法时都使用不同的Dog Bean,因此首先需要将Dog Bean配置为prototype作用域。
除此之外,不能直接使用普通依赖注入将Dog Bean注入Chinese bean中,还需要使用lookup方法注入来管理Dog BeanChinese bean之间的依赖关系。

使用lookup方法注入的步骤

为了使用lookup方法注入,大致需要如下两步。

  1. 将调用者Bean实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean
  2. <bean>元素中添加<lookup-method>子元素让Spring为调用者Bean的实现类实现指定的抽象方法。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\lookup-method
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Dog.java
├─impl\
│ ├─Chinese.java
│ └─GunDog.java
└─Person.java

Chinese.java

下面先将调用者Bean的实现类(Chinese)定义为抽象类,并定义一个抽象方法,该抽象方法用于获取被依赖的Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.crazyit.app.service.impl;

import org.crazyit.app.service.*;
// 抽象类
public abstract class Chinese implements Person
{
private Dog dog;
// 定义抽象方法,该方法用于获取被依赖Bean
public abstract Dog getDog();
public void hunt()
{
System.out.println("我带着:" + getDog() + "出去打猎");
System.out.println(getDog().run());
}
}

上面程序中定义了一个抽象的getDog()方法,在通常情况下,程序不能调用这个抽象方法,程序也不能使用抽象类创建实例。
接下来需要在配置文件中为<bean>元素添加<lookup-method>子元素,<lookup-method>子元素告诉Spring需要实现哪个抽象方法。 Spring为抽象方法提供实现体之后,这个方法就会变成具体方法,这个类也就变成了具体类,接下来Spring就可以创建该Bean的实例了。

lookup-method元素属性

使用<lookup-method>元素需要指定如下两个属性。

属性 描述
name 指定需要让Spring实现的方法。
bean 指定Spring实现该方法的返回值

beans.xml

下面是该应用的配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<!-- Spring只要检测到lookup-method元素,
Spring会自动为该元素的name属性所指定的方法提供实现体。-->
<lookup-method name="getDog" bean="gunDog"/>
</bean>
<!-- 指定gunDog Bean的作用域为prototype,
希望程序每次使用该Bean时用到的总是不同的实例 -->
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog"
scope="prototype">
<property name="name" value="旺财"/>
</bean>
</beans>

上面程序中的粗体字代码指定Spring应该负责实现getDog()方法,该方法的返回值是容器中的gunDog Bean实例。

Spring实现方法的逻辑

在通常情况下,Java类里的所有方法都应该由程序员来负责实现,系统无法为任何方法提供实现。但在有些情况下,系统可以实现一些极其简单的方法,例如,此处Spring将负责实现getDog()方法, Spring实现该方法的逻辑是固定的,它总采用如下代码来实现该方法:

1
2
3
4
5
6
7
8
// Spring要实现哪个方法由lookup-method元素的name属性指定
public Dog getDog()
{
// 获取Spring容器ctx
...
// 下面代码中"gunDog"有lookub-method元素的bean属性指定
return ctx.getBean("gunDog");
}

从上面的方法实现来看,程序每次调用Chinese对象的getDog()方法时,该方法将可以获取最新的gunDog对象。

Spring实现方法的方式

Spring会采用运行时动态增强的方式来实现<lookup-method>.元素所指定的抽象方法,

  • 如果目标抽象类(如上Chinese类)实现过接口, Spring会采用**JDK动态代理来**实现该抽象类,并为之实现抽象方法;
  • 如果目标抽象类(如上Chinese类)没有实现过接口,Spring会采用cglib实现该抽象类,并为之实现抽象方法。 Spring5.0spring-core-xxx.jar包中已经集成了cglib类库,无须额外添加cgibJAR包.

SpringTest.java

主程序两次获取chinese这个Bean,并通过该Bean来执行hunt方法,将可以看到每次请求时所使用的都是全新的Dog实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package lee;

import org.springframework.context.*;
import org.springframework.context.support.*;
import org.crazyit.app.service.*;

public class SpringTest
{
public static void main(String[] args)
{
// 以类加载路径下的beans.xml作为配置文件,创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
Person p1 = ctx.getBean("chinese", Person.class);
Person p2 = ctx.getBean("chinese", Person.class);
// 由于chinese Bean是singleton行为,
// 因此程序两次获取的chinese Bean是同一个实例。
System.out.println(p1 == p2);
p1.hunt();
p2.hunt();
}
}

由于getDog()方法由Spring提供实现, Spring保证每次调用getDog()时都会返回最新的gunDog实例。

运行结果

执行上面的程序,将看到如下运行结果:

1
2
3
4
5
true
我带着:org.crazyit.app.service.impl.GunDog@757942a1出去打猎
我是一只叫旺财的猎犬,奔跑迅速...
我带着:org.crazyit.app.service.impl.GunDog@4a87761d出去打猎
我是一只叫旺财的猎犬,奔跑迅速...

执行结果表明:使用lookup方法注入后,系统每次调用getDog()方法时都将生成一个新的gunDog实例,这就可以保证当singleton作用域的Bean需要prototype Bean实例时,直接调用getDog()方法即可获取全新的实例,从而可避免一直使用最早注入的Bean实例。

lookup注入的目标Bean必须设为prototype作用域

要保证lookup方法注入每次产生新的Bean实例,必须将目标Bean部署成prototype作用域;否则,如果容器中只有一个被依赖的Bean实例,即使采用lookup方法注入,每次也依然返回同一个Bean实例。

7.9.2 Bean销毁之前的行为

与定制初始化行为相似, Spring也提供两种方式定制Bean实例销毁之前的特定行为,这两种方式如下。

  1. 使用destroy-method属性。
  2. 实现DisposableBean接口。

第一种方式:使用destroy-method属性指定某个方法在Bean销毁之前被自动执行。使用这种方式不需要将代码与Spring的接口耦合在一起,代码污染小。
第二种方式:就是实现Disposable Bean接口,重写该接口内的方法destroy()方法,该方法定义为:void destroy() throws Exception

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\lifecycle-destroy
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ └─SteelAxe.java
└─Person.java

Chinese.java

下面的示例程序让Chinese类既包括一个普通方法,但在配置时将该普通方法指定为生命周期方法;也让该Chinese类实现DisposableBean接口,因此也包含一个destroy()生命周期方法。该Chinese类的代码如下。

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
package org.crazyit.app.service.impl;

import org.springframework.beans.factory.DisposableBean;
import org.crazyit.app.service.*;

public class Chinese implements Person, DisposableBean
{
private Axe axe;
public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
public void setAxe(Axe axe)
{
System.out.println("Spring执行依赖关系注入...");
this.axe = axe;
}
public void useAxe()
{
System.out.println(axe.chop());
}
public void close()
{
System.out.println("正在执行销毁之前的方法 close...");
}
public void destroy() throws Exception
{
System.out.println("正在执行销毁之前的方法 destroy...");
}
}

上面程序中的粗体字代码定义了一个普通的close()方法,实际上这个方法的方法名是任意的,并不定是close()Spring也不会对这个close()方法进行任何特别的处理。只是配置文件使用destroy-method属性指定该方法是一个”生命周期方法”。
增加destroy-method="close"来指定close方法应在Bean实例销毁之前自动执行,如果它不实现DisposableBean接口,上面的Chinese类没有实现任何Spring的接口,只是增加一个普通的closed方法,它依然是一个普通的Java文件,没有代码污染。
上面程序中的destroy()方法是实现DisposableBean接口必须实现的方法。

beans.xml

在配置文件中增加destroy-method="close",指定close方法应该在Bean实例销毁之前自动被调用。
配置文件代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe"/>
<!-- 配置chinese Bean,使用destroy-method="close"
指定该Bean实例被销毁之前,Spring会自动执行指定该Bean的close方法 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
destroy-method="close">
<property name="axe" ref="steelAxe"/>
</bean>
</beans>

配置该Bean与配置普通Bean没有任何区别, Spring可以自动检测容器中的DisposableBean,在销毁Bean实例之前, Spring容器会自动调用该Bean实例的destroy()方法。
singleton作用域的Bean通常会随容器的关闭而销毁,但问题是:ApplicationContext容器在什么时候关闭呢?

Web应用关闭时会自动关闭Spring容器

在基于WebApplicationContext实现中,系统已经提供了相应的代码保证关闭Web应用时恰当地关闭Spring容器。

在非Web环境如何关闭Spring容器

如果处于一个非Web应用的环境下,为了让Spring容器优雅地关闭,并调用singleton Bean上的相应析构回调方法,则需要在JVM里注册一个关闭钩子( shutdown hook),这样就可保证Spring容器被恰当关闭,且自动执行singleton Bean实例的析构回调方法。

如何注册关闭钩子来关闭Spring容器

为了注册关闭钩子,只需要调用在AbstractApplicationContext中提供的registerShutdownHook()方法即可。

BeanTest.java

看如下主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package lee;

import org.crazyit.app.service.Person;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanTest
{
public static void main(String[] args)
{
// 以CLASSPATH路径下的配置文件创建ApplicationContext
@SuppressWarnings("resource")
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
// 获取容器中的Bean实例
Person p = ctx.getBean("chinese", Person.class);
p.useAxe();
// 为Spring容器注册关闭钩子
ctx.registerShutdownHook();
}
}

上面程序的最后一行粗体字代码为Spring容器注册了一个关闭钩子,程序将会在退出JVM之前优雅地关闭Spring容器,并保证关闭Spring容器之前调用singleton Bean实例的析构回调方法。

运行结果

运行上面的程序,将可以看到程序退出执行输出如下两行:

1
2
3
...
正在执行销毁之前的方法 destroy...
正在执行销毁之前的方法 close...

通过上面的执行结果可以看出,在Spring容器关闭之前,注入之后,程序先调用destroy()方法进行回收资源,再调用close()方法进行回收资源。

不推荐实现DisposableBean接口方式

如果某个Bean类实现了DisposableBean接口,在Bean实例被销毁之前, Spring容器会自动调用该Bean实例的destroy()方法。其执行结果与采用destroy-method属性指定生命周期方法几乎一样。但实现DisposableBean接口污染了代码,是侵入式设计,因此不推荐采用。
对于实现Disposable Bean接口的Bean,无须使用destroy-method属性来指定销毁之前的方法,配置该Bean实例与配置普通Bean实例完全一样, Spring容器会自动检测Bean实例是否实现了特定生命周期接口,并由此决定是否需要执行生命周期方法。

先执行DisposableBean接口方法再执行destroy-method属性指定的方法

如果既采用destroy-method属性指定销毁之前的方法,又采用实现DisposableBean接来指定销毁之前的方法, Spring容器会执行两个方法:先执行DisposableBean接口中定义的方法,然后执行destroy-method属性指定的方法

在beans元素上为所有bean指定生命周期行为

除此之外,如果容器中的很多Bean都需要指定特定的生命周期行为,则可以利用<beans>元素的default-init-method属性和default-destroy-method属性,这两个属性的作用类似于<bean>元素的init-methoddestroy-method属性的作用,区别是default-init-method属性和default-destroy- method属性是属于<beans>元素的,它们将使容器中的所有Bean生效。

1
2
3
<beans default-init-method="init">
...
</beans>

上面配置片段中的代码指定了default-init-method="init",这意味着只要Spring容器中的某个Bean实例具有init()方法, Sprin就会在该Bean的所有依赖关系被设置之后,自动调用该Bean实例的init()方法。

Spring容器中Bean实例完整的生命周期行为

图7.12显示了Spring容器中Bean实例完整的生命周期行为:
这里有一张图片

7.9.1 依赖关系注入之后的行为

Spring提供两种方式在Bean全部属性设置成功后执行特定行为。

  • 使用init-method属性
  • 实现InitializingBean接口

第一种方式:使用init-method属性指定某个方法应在Bean全部依赖关系设置结束后自动执行。使用这种方式不需要将代码与Spring的接口耦合在一起,代码污染小。
第二种方式:也可达到同样的效果,就是让Bean类实现InitializingBean接口,该接口提供一个方法:void afterPropertiesSet() throws ExceptionSpring容器会在为该Bean注入依赖关系之后,调用该Bean所实现的afterPropertiesSet()方法。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\lifecycle-init
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ └─SteelAxe.java
└─Person.java

Chinese.java

下面是该Bean实现类的代码。

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
package org.crazyit.app.service.impl;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.*;
import org.springframework.context.*;
import org.crazyit.app.service.*;

public class Chinese
implements
Person,
InitializingBean,
BeanNameAware,
ApplicationContextAware
{
private Axe axe;
public void setBeanName(String beanName)
{
System.out.println("===setBeanName===");
}
public void setApplicationContext(ApplicationContext ctx)
{
System.out.println("===setApplicationContext===");
}
public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
// axe的setter方法
public void setAxe(Axe axe)
{
System.out.println("Spring调用setAxe()执行依赖注入...");
this.axe = axe;
}
public void useAxe()
{
System.out.println(axe.chop());
}
// 测试用的初始化方法
public void init()
{
System.out.println("正在执行初始化方法 init...");
}
// 实现InitializingBean接口必须实现的方法
public void afterPropertiesSet() throws Exception
{
System.out.println("正在执行初始化方法 afterPropertiesSet...");
}
}

上面程序中的粗体字代码定义了一个普通的init方法,实际上这个方法的方法名是任意的,并不定是init0, Spring也不会对这个init()方法进行任何特别的处理。只是配置文件使用init-method属性指定该方法是一个”生命周期方法”。
增加init-method="init"来指定init()方法应在Bean全部属性设置结束后自动执行,如果它不实现InitializingBean接口,上面的Chinese类没有实现任何Spring的接口,只是增加一个普通的init()方法,它依然是一个普通的Java文件,没有代码污染。

实现Initializingbean接口的Bean类必须实现afterPropertiesSet()方法。
除此之外,该实现类还实现了Spring提供的BeanNameAwareApplicationContextAware接口,并实现了这两个接口中定义的setBeanName()setApplicationContext()方法,这样即可观察到Spring容器在创建Bean实例、调用setter方法执行依赖注入、执行完setBeanName()setApplicationContext()方法之后,自动执行Bean的初始化行为(包括init-method指定的方法和afterPropertiesSet()方法)。

beans.xml

下面的配置文件指定org.crazyit.app.service.impl.Chineseinit()方法是一个生命周期方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<!-- 配置chinese Bean,使用init-method="init"
指定该Bean所有属性设置完成后,自动执行init方法 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
init-method="init">
<property name="axe" ref="steelAxe" />
</bean>
</beans>

运行结果

1
2
3
4
5
6
7
8
Spring实例化依赖bean:SteelAxe实例...
Spring实例化主调bean:Chinese实例...
Spring调用setAxe()执行依赖注入...
===setBeanName===
===setApplicationContext===
正在执行初始化方法 afterPropertiesSet...
正在执行初始化方法 init...
钢斧砍柴真快

代码分析

通过上面的执行结果可以看出,当SpringsteelAxe注入chinese bean(即完成依赖注入)之后,以及调用了setBeanName()setApplicationContext()方法之后, Spring先调用该BeanafterPropertiesSet()方法进行初始化,再调用init-method属性所指定的方法进行初始化
对于实现InitializingBean接口的Bean,无须使用init-method属性来指定初始化方法,配置该Bean实例与配置普通Bean实例完全一样, Spring容器会自动检测Bean实例是否实现了特定生命周期接口,并由此决定是否需要执行生命周期方法。

不推荐实现InitializingBean接口方式

如果某个Bean类实现了InitializingBean接口,当该Bean的所有依赖关系被设置完成后, Spring容器会自动调用该Bean实例的afterPropertiesSet()方法。其执行结果与采用init-method属性指定生命周期方法几乎一样。但实现InitializingBean接口污染了代码,是侵入式设计,因此不推荐采用。

既使用init-method属性又实现InitializingBean接口时

如果既采用init-method属性指定初始化方法,又实现InitializingBean接口来指定初始化方法, Spring容器会执行两个初始化方法:先执行InitializingBean接口中定义的方法,然后执行init-method属性指定的方法