7.5.10 Spring的Bean和JavaBean

Spring容器对Bean没有特殊要求,甚至不要求该Bean像**标准的JavaBean(标准的JavaBean要求必须为每个属性提供对应的gettersetter方法)**。

  • Spring中的BeanJava实例、Java组件;
  • 而传统Java应用中的JavaBean通常作为DTO(数据传输对象),用来封装值对象,在各层之间传递数据。

程序示例

项目结构

1
2
3
4
5
6
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\DataSource
├─data.sql
└─src\
├─beans.xml
└─lee\
└─BeanTest.java

data.sql

1
2
3
4
5
6
7
8
9
10
drop database if exists spring;
create database spring;
use spring;

create table news_inf
(
news_id int auto_increment primary key,
news_title varchar(255),
news_content varchar(255)
);

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
<?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,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/spring?useSSL=true"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="root"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="200"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="2"/>
<!-- 指定连接数据库连接池的初始连接数 -->
<property name="initialPoolSize" value="2"/>
<!-- 指定连接数据库连接池的连接的最大空闲时间 -->
<property name="maxIdleTime" value="200"/>
</bean>
</beans>

主程序部分由Spring容器来获取该Bean的实例,获取实例时使用Bean的唯一标识:id属性,id属性是Bean实例在容器中的访问点。下面是主程序代码。

BeanTest.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
package lee;

import javax.sql.DataSource;
import java.sql.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanTest
{
public static void main(String[] args)
throws Exception
{
// 实例化Spring容器。Spring容器负责实例化Bean
@SuppressWarnings("resource")
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
// 获取容器中id为dataSource的Bean
DataSource ds = ctx.getBean("dataSource", DataSource.class);
// 通过DataSource来获取数据库连接
Connection conn = ds.getConnection();
// 通过数据库连接获取PreparedStatement
PreparedStatement pstmt = conn.prepareStatement(
"insert into news_inf values(null , ? , ?)");
pstmt.setString(1 , "疯狂Java联盟成立了");
pstmt.setString(2 , "疯狂Java地址:www.crazyit.org");
// 执行SQL语句
pstmt.executeUpdate();
// 清理资源,回收数据库连接资源。
if (pstmt != null)pstmt.close();
if (conn != null)conn.close();
}
}

上面程序从Spring容器中获得了一个DataSource对象,通过该DataSource对象就可以获取简单的数据库连接。执行上面程序,将看到spring数据库的news_inf数据表中多了一条记录。
从该实例可以看出, SpringBean远远超出值对象的JavaBean范畴,Bean可以代表应用中的任何组件、任何资源实例

Spring中的Bean要满足的原则

虽然SpringBean没有特殊要求,但依然建议Spring中的Bean应满足如下几个原则。

  1. 尽量为每个Bean实现类提供无参数的构造器。
  2. 接受构造注入的Bean,则应提供对应的、带参数的构造函数。
  3. 接受设值注入的Bean,则应提供对应的setter方法,并不要求提供对应的getter方法。

Java Bean和Spring中的Bean的区别

传统的Java BeanSpring中的Bean存在如下区别。

  1. 用处不同:传统的JavaBean更多是作为值对象传递参数; SpringBean用处几乎无所不包任何应用组件都被称为Bean
  2. 写法不同:传统的JavaBean作为值对象,要求每个属性都提供gettersetter方法;但SpringBean只需为接受设值注入的属性提供setter方法即可。
  3. 生命周期不同:传统的JavaBean作为值对象传递,不接受任何容器管理其生命周期; Spring中的BeanSpring管理其生命周期行为。

7.5.9 组合属性

Spring还支持组合属性的方式。例如,使用配置文件为形如foo.bar.name的属性设置参数值。Bean的组合属性设置参数值时,除最后一个属性之外,其他属性值都不允许为null
例如有如下的Bean

项目结构

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

ExampleBean.java

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

public class ExampleBean
{
// 定义一个Person类型的成员变量
private Person person = new Person();
// person的getter方法
public Person getPerson()
{
return this.person;
}
}

上面Example Bean里提供了一个person成员变量,该person变量的类型是Person类, Person是个Java类, Person类里有一个String类型的name属性(有name实例变量及对应的getter, setter方法),则可以使用组合属性的方式为Example Beanpersonname指定值。配置文件如下。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
<?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="exampleBean" class="org.crazyit.app.service.ExampleBean">
<!-- 驱动Spring调用exampleBean的getPerson().setName()方法,
以"孙悟空"作为参数 -->
<property name="person.name" value="孙悟空"/>
</bean>
</beans>

通过使用这种组合属性的方式, Spring允许直接为Bean实例的复合类型的属性指定值。但这种设置方式有一点需要注意:使用组合属性指定参数值时,除了最后一个属性外,其他属性都不能为null,否则将引发NullPointerException异常。例如,上面配置文件为person.name指定参数值,则example BeangetPerson()方法的返回值一定不能为null
对于这种注入组合属性值的形式,每个<property>元素依然是让Spring执行一次setter方法,但它不再直接调用该Beansetter方法,而是需要先调用getter方法,然后再去调用setter方法。例如上面的粗体字配置代码,相当于让Spring执行如下代码:
exampleBean.getperson().setName("孙悟空");
也就是说,组合属性只有最后一个属性才调用setter方法,前面各属性实际上对应于调用getter方法—这也是前面属性都不能为null的缘由
例如有如下配置片段:

1
2
3
<bean id="a" class="org.crazyit.app.service.AClass">
<property name="foo.bar.x.y" value="yyy"/>
</bean>

上面的组合属性注入相当于让Spring执行如下代码:

1
a.getFoo().getBar().getX().setY("yyy");

7.5.8 注入集合值

如果需要调用形参类型为集合的setter方法,或调用形参类型为集合的构造器,则可使用集合元素<list><set><map><props>分别来设置类型为ListSetMapProperties的集合参数值。
下面先定义一个包含大量集合属性的Java类,配置文件将会通过上面那些元素来为这些集合属性设置属性值。看如下Java类的代码。

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\collection
└─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
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
package org.crazyit.app.service.impl;

import java.util.*;

import org.crazyit.app.service.*;

public class Chinese implements Person
{
// 下面是系列集合类型的成员变量
private List<String> schools;
private Map scores;
private Map<String , Axe> phaseAxes;
private Properties health;
private Set axes;
private String[] books;

public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}

// schools的setter方法
public void setSchools(List schools)
{
this.schools = schools;
}
// scores的setter方法
public void setScores(Map scores)
{
this.scores = scores;
}
// phaseAxes的setter方法
public void setPhaseAxes(Map<String , Axe> phaseAxes)
{
this.phaseAxes = phaseAxes;
}
// health的setter方法
public void setHealth(Properties health)
{
this.health = health;
}
// axes的setter方法
public void setAxes(Set axes)
{
this.axes = axes;
}
// books的setter方法
public void setBooks(String[] books)
{
this.books = books;
}

// 访问上面全部的集合类型的成员变量
public void test()
{
System.out.println(schools);
System.out.println(scores);
System.out.println(phaseAxes);
System.out.println(health);
System.out.println(axes);
System.out.println(java.util.Arrays.toString(books));
}
}

在上面的Chinese类中定义了6个集合类型的成员变量。下面分别为<property>元素增加<list><set><map><props>子元素来配置这些集合类型的参数值。
下面是Spring的配置文件

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
<?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">
<!-- 定义2个普通Axe Bean -->
<bean id="stoneAxe" class="org.crazyit.app.service.impl.StoneAxe"/>
<bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe"/>
<!-- 定义chinese Bean -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<property name="schools">
<!-- 为调用setSchools()方法配置List集合作为参数值 -->
<list>
<!-- 每个value、ref、bean...都配置一个List元素 -->
<value>小学</value>
<value>中学</value>
<value>大学</value>
</list>
</property>
<property name="scores">
<!-- 为调用setScores()方法配置Map集合作为参数值 -->
<map>
<!-- 每个entry配置一个key-value对 -->
<entry key="数学" value="87"/>
<entry key="英语" value="89"/>
<entry key="语文" value="82"/>
</map>
</property>
<property name="phaseAxes">
<!-- 为调用setPhaseAxes()方法配置Map集合作为参数值 -->
<map>
<!-- 每个entry配置一个key-value对 -->
<entry key="原始社会" value-ref="stoneAxe"/>
<entry key="农业社会" value-ref="steelAxe"/>
</map>
</property>
<property name="health">
<!-- 为调用setHealth()方法配置Properties集合作为参数值 -->
<props>
<!-- 每个prop元素配置一个属性项,其中key指定属性名 -->
<prop key="血压">正常</prop>
<prop key="身高">175</prop>
</props>
<!--
<value>
pressure=normal
height=175
</value> -->
</property>
<property name="axes">
<!-- 为调用setAxes()方法配置Set集合作为参数值 -->
<set>
<!-- 每个value、ref、bean..都配置一个Set元素 -->
<value>普通的字符串</value>
<bean class="org.crazyit.app.service.impl.SteelAxe"/>
<ref bean="stoneAxe"/>
<!-- 为Set集合配置一个List集合作为元素 -->
<list>
<value>20</value>
<!-- 再次为List集合配置一个Set集合作为元素 -->
<set>
<value type="int">30</value>
</set>
</list>
</set>
</property>
<property name="books">
<!-- 为调用setBooks()方法配置数组作为参数值 -->
<list>
<!-- 每个value、ref、bean...都配置一个数组元素 -->
<value>疯狂Java讲义</value>
<value>疯狂Android讲义</value>
<value>轻量级Java EE企业应用实战</value>
</list>
</property>
</bean>
</beans>

上面的代码是配置集合类型的参数值的关键代码,从配置文件可以看出, SpringList集合和数组的处理是一样的,都用<list>元素来配置
当使用<list><set><map>.等元素配置集合类型的参数值时,还需要配置集合元素。由于集合元素又可以是基本类型值、引用容器中的其他Bean、嵌套Bean或集合属性等,所以<list><key><set>元素又可接受如下子元素:

  • <value>:指定集合元素是基本数据类型值或字符串类型值。
  • <ref>:指定集合元素是容器中的另一个Bean实例。
  • <bean>:指定集合元素是一个嵌套Bean
  • <list><set><map><props>:指定集合元素又是集合。

props元素说明

<props>元素用于配置Properties类型的参数值, Properties类型是一种特殊的类型,其keyvalue能是字符串,故Spring配置Properties类型的参数值比较简单:每个key-value对只要分别给出keyvalue就足够了;因为keyvalue都是字符串类型,所以使用如下格式的<prop>元素就够了:

1
2
3
4
<props>
<prop key="血压">正常</prop>
<prop key="身高">175</prop>
</props>

简化写法

Spring还提供了一个简化语法来支持Properties形参的setter方法,例如如下配置片段:

1
2
3
4
5
6
<property name="health">
<value>
pressure=normal
height=175
</value>
</property>

简化写法中不能出现中文

上面这种配置方式同样配置了两组属性,但这种配置语法有一个很大的限制:属性名、属性值都只能是英文或数字,不可出现中文!

map元素说明

当使用<map>元素配置Map参数值时比较复杂,因为Map集合的每个元素由keyvalue两个部分组成,所以配置文件中的每个<entry>配置一组key-value对,其中<entry>元素支持如下4个属性。

<entry>元素的属性 描述
key 如果Map key是基本类型值或字符串,则可使用该属性来指定Map key
key-ref 如果Map key是容器中的另一个Bean实例,则可使用该属性指定容器中其他Beanid
value 如果Map value是基本类型值或字符串,则可使用该属性来指定Map value;
value-ref 如果Map value是容器中的另一个Bean实例,则可使用该属性指定容器中其他Beanid

由于Map集合的keyvalue都可以是基本类型值引用容器中的其他Bean、嵌套Bean或集合属性等,所以也可以采用比较传统、比较臃肿的写法。例如,将上面关于scores属性的配置写成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<property name="scores">
<map>
<entry>
<key>
<value>数学</value>
</key>
<value>87</value>
</entry>
<entry>
<key>
<value>英语</value>
</key>
<value>89</value>
</entry>
<entry>
<key>
<value>语文</value>
</key>
<value>82</value>
</entry>
</map>
</property>

从上面配置可以看出,<key>元素专门用于配置Map集合的key-value对的key,又由于Map key又可能是基本类型、引用容器中已有的Bean、嵌套Bean、集合等,因此<key>的子元素又可以是valuerefbeanlistsetmapprops等元素。

集合的合并

Spring2.0开始, Spring loC容器将支持集合的合并,子Bean中的集合属性值可以从其父Bean的集合属性继承覆盖而来。也就是说,Bean的集合属性的最终值是父Bean、子Bean合并后的最终结果,而且子Bean集合中的元素可以覆盖父Bean集合中对应的元素
下面的配置片段示范了集合合并的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<beans>
<bean id=""parent abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@crazyit.org</prop>
<prop key="suppport">suppport@crazyit.org</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property id="child" name="adminEmails">
<props merge="true">
<prop key="sales">sales@crazyit.org</prop>
<prop key="support">support@crazyit.org</prop>
</props>
<property>
</bean>
</beans>

上面的配置片段中child Bean继承了parent Bean,并为<props>元素指定了merge="true",这将会把parent Bean的集合属性合并到child Bean中;
当进行合并时,由child Bean再次配置了名为support的属性,所以该属性将会覆盖parent Bean中的配置定义,于是child beanadminEmails属性值如下:

1
2
3
administrator=administratore@crazyit.org
sales=sales@crazyit.org
support=master@crazyit.org

泛型支持

JDK1.5以后,Java可以使用泛型指定集合元素的类型,则Spring可通过反射来获取集合元素的类型,这样Spring的类型转换器也会起作用了。
例如如下Java代码:

1
2
3
4
5
6
7
8
public class Test
{
private Map<string, Double> prices;
public void setPrices(Map<string Double> prices)
{
this.prices= prices;
}
}

上面的prices集合是Map集合,且程序使用泛型限制了MapkeyString,且valueDouble,则Spring可根据泛型信息把配置文件的集合参数值转换成相应的数据类型。例如如下配置片段:

1
2
3
4
5
6
7
8
<bean id="test" class="lee.Test">
<property name="prices">
<map>
<entry key="疯狂 Android讲义" vaue="99.0"/>
<entry key="疯狂Java讲义" value="109.0"/>
</map>
</property>
</bean>

7.5.7 注入嵌套Bean

如果某个Bean所依赖的Bean不想被Spring容器直接访问,则可以使用嵌套Bean.
<bean>配置成<property><constructor-args>的子元素,那么该<bean>元素配置的Bean仅仅作为setter注入、构造注入的参数,这种Bean就是嵌套Bean。由于容器不能获取嵌套Bean,因此它不需要指定id属性。

例如如下配置。

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="chinese" class="org.crazyit.app.service.impl.Chinese">
<!-- 驱动调用chinese的setAxe()方法,使用嵌套Bean作为参数 -->
<property name="axe">
<!-- 嵌套Bean配置的对象仅作为setter方法的参数 嵌套Bean不能被容器访问,因此无需指定id属性 -->
<bean class="org.crazyit.app.service.impl.SteelAxe" />
</property>
</bean>
</beans>

采用上面的配置形式可以保证嵌套Bean不能被容器访问,因此不用担心其他程序修改嵌套Bean。外部Bean的用法与之前的用法完全一样,使用结果也没有区别。

什么时候用嵌套Bean

嵌套Bean提高了程序的内聚性,但降低了程序的灵活性。只有在完全确定无须通过Spring容器访问某个Bean实例时,才考虑使用嵌套Bean来配置该Bean

使用嵌套Bean与使用ref引用容器中另一个Bean在本质上是一样的。
Spring框架的本质就是通过XML配置文件来驱动Java代码,当程序要调用setter方法或有参数的构造器时,程序总需要传入参数值,随参数类型的不同, Spring配置文件当然也要随之改变。

  1. 形参类型是基本类型、 String、日期等,直接使用value指定字面值即可。
  2. 形参类型是复合类(如PersonDogData Source等),那就需要传入一个Java对象作为实参,于是有三种方式:
    • 使用ref引用一个容器中已配置的Bean(Java对象);
    • 使用自动装配
    • 使用<bean>元素配置一个嵌套Bean(Java对象);

除此之外,形参类型还可能是SetListMap等集合,也可能是数组类型。接下来继续介绍如何在Spring配置文件中配置setListMap数组等参数值。

项目结构

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

7.5.6 使用自动装配注入合作者Bean

Spring能自动装配BeanBean之间的依赖关系,即无须使用ref显式指定依赖Bean,而是由Spring容器检査XML配置文件内容,根据某种规则,为调用者Bean注入被依赖的Bean

Spring的自动装配

Spring的自动装配
可通过<beans>元素的default-autowire属性指定,该属性对配置文件中所有的Bean起作用;
也可通过<bean.>元素的autowire属性指定,该属性只对该Bean起作用。

从上面介绍不难发现:在同一个Spring容器中完全可以让某些Bean使用自动装配,而另一些Bean不使用自动装配。
自动装配可以减少配置文件的工作量,但降低了依赖关系的透明性和清晰性。

autowire属性值

autowiredefault-autowire属性可以接受如下值。

属性值 描述
no 不使用自动装配。Bean依赖必须通过ref元素定义。这是默认的配置,在较大的部署环境中不鼓励改变这个配置,显式配置合作者能够得到更清晰的依赖关系。
byName 根据setter方法名进行自动装配。 Spring容器查找容器中的全部Bean,找出其idsetter方法名去掉set前缀,并小写首字母后的同名的Bean来完成注入。如果没有找到匹配的Bean实例,则Spring不会进行任何注入。
byType 根据setter方法的形参类型来自动装配。 Spring容器查找容器中的全部Bean,如果正好有一个Bean的类型与setter方法的形参类型匹配,就自动注入这个Bean;如果找到多个这样的Bean,就抛出一个异常;如果没找到这样的Bean,则什么都不会发生, setter方法不会被调用。
constructor byType类似,区别是用于自动匹配构造器的参数。如果容器不能恰好找到一个与构造器参数类型匹配的Bean,则会抛出一个异常。
autodetect Spring容器根据Bean内部结构,自行决定使用constructor还是使用byType策略。如果找到一个默认的构造函数,那么就会应用byType策略。

由此可见, byName策略是根据setter方法的方法名与Beanid进行匹配;byType策略是根据setter方法的参数类型Bean的类型进行匹配。

7.5.6.1 byName规则

byName规则是指setter方法的方法名与Beanid进行匹配,假如Bean A的实现类包含setB()方法,而Spring的配置文件恰好包含idbBean,则Spring容器会将b实例注入Bean A中。如果容器中没有名字匹配的Bean,则Spring不会做任何事情。

程序示例

项目结构

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

beans.xml

看如下配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
<?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">
<!-- 指定使用byName自动装配策略,Spring会根据setter方法的方法名与Bean的id进行匹配 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
autowire="byName" />
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog">
<property name="name" value="wangwang" />
</bean>
</beans>

Chinese.java

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

import org.crazyit.app.service.*;

public class Chinese implements Person
{
private Dog dog;
// dog的setter方法
public void setGunDog(Dog dog)
{
this.dog = dog;
}
public Dog getDog()
{
return this.dog;
}
public void test()
{
System.out.println("我是一个普通人,养了一条狗:"
+ getDog().run());
}
}

上面的配置文件指定了byName自动装配策略,而且Chinese类恰好有如下setter方法:

1
2
3
4
5
// dog的setter方法
public void setGunDog(Dog dog)
{
this.dog = dog;
}

上面setter方法的方法名为setGunDog, Spring容器就会寻找容器中idgunDogBean,

  • 如果能找到这样的Bean,该Bean就会作为调用setGunDog()方法的参数;
  • 如果找不到这样的Bean, Spring就不调用setGunDog()方法

7.5.6.2 byType规则

byType规则是根据setter法的参数类型与Bean的类型进行匹配:

  • 假如A实例有setB(B b)方法,而Spring的配置文件中恰好有一个类型为BBean实例,则容器为A注入类型匹配的Bean实例;
  • 如果容器中没有类型为B的实例, 则Spring不会调用setB()方法;
  • 但如果容器中包含多于一个的B实例,则程序将会抛出异常。

程序示例

项目结构

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

beans.xml

看如下配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
<?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">
<!-- 指定使用byType自动装配策略,Spring会根据setter方法的参数类型与Bean的类型进行匹配 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
autowire="byType" />
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog">
<property name="name" value="wangwang" />
</bean>
</beans>

上面的配置文件指定了byType自动装配策略,而且Chinese类恰好有如下setter方法。

1
2
3
4
5
// dog的setter方法
public void setDog(Dog dog)
{
this.dog = dog;
}

只有一个类型的Bean对象的情况

上面setter法的形参类型是Dog,而Spring容器中的org.crazyit.app.service.impl.GunDog类实现了Dog接口,因此Spring会以该Bean为参数来调用chinesesetDog方法。

有两个同类型的Bean对象的情况

但如果在配置文件beans.xml中再配置一个Bean,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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">
<!-- 指定使用byType自动装配策略,Spring会根据setter方法的参数类型与Bean的类型进行匹配 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
autowire="byType" />
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog">
<property name="name" value="wangwang" />
</bean>
<bean id="petDog" class="org.crazyit.app.service.impl.PetDog">
<property name="name" value="ohoh" />
</bean>
</beans>

此时, Spring将无法按byType策略进行自动装配,因为容器中有两个类型为DogBean,Spring无法确定应为chinese bean注入哪个Bean,所以程序将抛出异常.

同时指定自动装配和使用ref显示指定依赖时

当一个Bean既使用自动装配依赖,又使用ref显式指定依赖时,则使用ref显式指定的依赖覆盖自动装配依赖。在如下配置文件中:

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">
<!-- 指定使用byType自动装配策略,Spring会根据setter方法的参数类型与Bean的类型进行匹配 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
autowire="byType">
<property name="dog" ref="petDog" />
</bean>
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog">
<property name="name" value="wangwang" />
</bean>
<bean id="petDog" class="org.crazyit.app.service.impl.PetDog">
<property name="name" value="ohoh" />
</bean>
</beans>

因为使用ref显式指定的依赖关系将覆盖自动装配的依赖关系,所以自动转配失效,程序不会抛出异常.

对于大型的应用,不鼓励使用自动装配。虽然使用自动装配可减少配置文件的工作量,但大大降低了依赖关系的清晰性和透眀性。依赖关系的装配依赖于源文件的属性名或属性类型,导致BeanBean之间的耦合降低到代码层次,不利于高层次解耦

将某些Bean排除在自动装配之外

在某些情况下,程序希望将某些Bean排除在自动装配之外,不作为Spring自动装配策略的候选者此时可设置autowire-candidate属性,通过为<bean>元素设置autowire-candidate="false",即可将该Bean排除在自动装配之外,容器在查找自动装配Bean时将不考虑该Bean:
除此之外,还可通过在<beans>元素中指定defaul-autowire-candidates属性将一批Bean排除在自动装配之外。 default-autowire-candidates属性的值允许使用模式字符串,例如指定default-autowire-candidates="*abc",则所有以"abc"结尾的Bean都将被排除在自动装配之外。不仅如此,该属性甚至可以指定多个模式字符串,这样所有匹配任一模式字符串的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 指定使用byType自动装配策略,Spring会根据setter方法的参数类型与Bean的类型进行匹配 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
autowire="byType">
</bean>
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog">
<property name="name" value="wangwang" />
</bean>
<!-- 配置petDog Bean,其实现类也实现了Dog接口 -->
<bean id="petDog" class="org.crazyit.app.service.impl.PetDog"
autowire-candidate="false">
<property name="name" value="ohoh" />
</bean>
</beans>

这样petDog这个被排除在自动转配之外,则此时只有gunDog符合byType自动装配要求,程序依然可以正常运行.

剩下的两种自动装配策略与byNamebyType大同小异,此处不再赘述。

7.5.5 配置合作者Bean

如果需要为Bean设置的属性值是容器中的另一个Bean实例,则应该使用<ref>元素。使用<ref>元素时可指定一个bean属性,该属性用于引用容器中其他Bean实例的id属性值

使用property的ref子元素引入其他Bean

看下面的配置片段:

1
2
3
4
5
6
7
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<property name="axe">
<ref bean="steelAxe" />
</property>
</bean>

使用property元素的ref属性

与注入普通属性值类似的是,注入合作者Bean也有一种简洁的写法,看如下的配置方式:

1
2
3
4
5
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<property name="axe" ref="steelAxe" />
</bean>

通过为<property>元素增加ref属性,一样可以将容器中另一个Bean作为调用setter方法的参数这种简洁写法的配置效果与前面使用<ref>元素的效果完全相同。

构造注入的情况

<constructor-arg>元素也可增加ref属性,从而指定将容器中另一个Bean作为构造器参数。

7.5.4 设置普通属性值

使用元素的value属性

<property>元素的value属性用于指定基本类型及其包装类字符串类型的参数值, Spring使用XML解析器来解析出这些数据,然后利用java.beans.PropertyEditor完成类型转换:从java.lang.String类型转换为所需的参数值类型。如果目标类型是基本类型及其包装类,通常都可以正确转换。

程序示例

项目结构

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

下面的代码演示了**<property>元素的value属性**确定属性值的情况。假设有如下的Bean类,该Bean类里包含int型和double型的两个属性,并为这两个属性提供对应的setter方法。下面是该Bean的实现类代码,由于仅仅用于测试注入普通属性值,因此没有使用接口.

ExampleBean.java

1
2
3
4
5
6
7
8
9
package org.crazyit.app.service;
public class ExampleBean
{
// 定义一个int型的成员变量
private int integerField;
// 定义一个double型的成员变量
private double doubleField;
// 此处省略getter和setter方法,请自己补上
}

上面Bean类的两个成员变量都是基本数据类型的, Spring配置文件使用<property>元素的value属性即可为这两个基本数据类型的成员变量对应的setter方法指定参数值。配置文件如下。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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="exampleBean" class="org.crazyit.app.service.ExampleBean">
<!-- 指定int型的参数值 -->
<property name="integerField" value="1" />
<!-- 指定double型的参数值 -->
<property name="doubleField" value="2.3" />
</bean>
</beans>

BeanTest.java

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

import org.springframework.context.*;
import org.springframework.context.support.*;
import org.crazyit.app.service.*;
public class BeanTest
{
public static void main(String[] args)
{
// 以类加载路径下的bean.xml文件来创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
ExampleBean b = ctx.getBean("exampleBean", ExampleBean.class);
System.out.println(b.getIntegerField());
System.out.println(b.getDoubleField());
}
}

运行程序,输出exampleBean的两个成员变量值,可以看到输出结果分别是12.3,这表明Spring已为这两个成员变量成功注入了值。

使用元素的子元素

早期Spring还支持一个”更为臃肿”的写法,例如需要配置一个驱动类,如果用早期Spring的配置方式,则需要如下三行代码:

1
2
3
<property name="driverClass">
<value>com.mysql.jdbc.Driver</value>
</property>

而现在的Spring配置则使用如下一行代码接口实现:

1
<property name="driverClass" value="com.mysql.jdbc.Driver"/>

上面的这一行配置片段通过为<property>元素的value属性来指定调用setDriverClass()方法的参数值为com.mysql.jdbc.Driver,从而完成依赖关系的设值注入。这种配置方式只要一行代码即可完成一次”依赖注入”
两种方式所能提供的信息量完全一样,而采用value属性则更加简洁;所以后来Spring都推荐采用value属性的方式来配置。

7.5.3 配置依赖

根据前面的介绍,Java应用中各组件相互调用的实质可以归纳为依赖关系,根据注入方式的不同,Bean的依赖注入通常有如下两种形式。

  1. 设值注入:通过<property>元素驱动Spring执行setter方法。
  2. 构造注入:通过<constructor-arg>元素驱动Spring执行带参数的构造器。

不管是设值注入,还是构造注入,都视为Bean的依赖,接受Spring容器管理,依赖关系的值要么是一个确定的值,要么是Spring容器中其他Bean的引用
通常不建议使用配置文件管理Bean的基本类型的属性值;通常只使用配置文件管理容器中BeanBean之间的依赖关系。
对于singleton作用域的Bean,如果没有强行取消其预初始化行为,系统会在创建Spring容器时预初始化所有的singleton Bean,与此同时,该Bean所依赖的Bean也被一起实例化。
BeanFactoryApplicationContext实例化容器中Bean的时机不同:前者等到程序需要Bean实例时才创建Bean:而后者在容器创建Application Context实例时,会预初始化容器中所有的singleton Bean

因为采用ApplicationContext作为Spring容器,创建容器时会同时创建容器中所有singleton作用域的Bean,因此可能需要更多的系统开销。但一旦创建成功,应用后面的响应速度更快,因此,对于普通的Java EE应用,推荐使用ApplicationContext作为Spring容器.
创建BeanFactory时不会立即创建Bean实例,所以有可能程序可以正确地创建BeanFactory实例,但当请求Bean实例时依然抛出一个异常:创建Bean实例或注入它的依赖关系时出现错误。
配置错误的延迟出现,也会给系统引入不安全因素,而ApplicationContext则默认预实例化所有singleton作用域的Bean,所以ApplicationContext实例化过程比BeanFactory实例化过程的时间和内存开销大,但可以在容器初始化阶段就检验出配置错误。
除此之外, Spring可以为任何Java对象注入任何类型的属性—只要该Java对象为该属性提供了应的setter方法即可。
例如,如下配置片段:

1
2
3
4
<bean id="id" class="lee.AClass">
<property name="aaa" value="aValue">
<property name="bbb" value="bValue">
</bean>

对于上面的配置片段,有效的数据只是那些粗体字内容, Spring将会为每个<bean>元素创建一个Java对象—这个Java对象就是一个Bean实例。对于上面的程序, Spring将采用类似于如下的代码创建Java实例。

1
2
3
4
// 获取lee.AClass类的Class对象
Class targetClass=Class.forName("lee.AClass");
// 调用lee.AClass类的无参构造器创建对象
Object bean=targetClass.newInstance();

创建该实例后, Spring接着遍历该<bean>元素里所有的<property>子元素,<bean>元素每包含一个<property>子元素, Spring就为该bean实例调用一次setter方法。对于上面第一行<property>子元素,将有类似的如下代码:

1
2
3
4
5
// 获取第一个<property>元素的name属性值对应的 setter方法名
String setName1="set"+"Aaa";
// 获取lee.Class类里的setAaa()方法
Method setMethod1=targetClass.getMethod(setName1,aValue.getClass());
setMethod1.invoke(bean,aValue);

通过类似上面的代码, Spring就可根据配置文件的信息来创建Java实例,并调用该Java实例的setter方法(这就是所谓的设值注入)
对于如下配置片段:

1
2
3
4
<bean id="id" class="lee.AClass">
<constructor-arg index="1" value="aValue"/>
<constructor-arg index="0" value="bValue"/>
</bean>

上面的配置片段指定了两个<constructor-arg/>子元素, Spring就不再采用默认的构造器来创建Bean实例,而是使用特定构造器来创建该Bean实例。
Spring将会采用类似如下的代码来创建Bean实例:

1
2
3
4
5
6
// 获取lee.AClass类的Class对象
Class targetClass=Class.forName("lee.AClass");
// 获取第一个参数是bValue类型,第二个参数是aValue类型的构造器
Constructor targetCtr=targetClass.getConstructor(bValue.getClass(),aValue.getClass());
// 以指定构造器创建Bean实例
Object bean=targetCtr.newInstance(bValue,aValue);

上面的程序片段仅是一个示例, Spring实际上还需要根据<property>元素、<contructor-arg>元素所使用的value属性、ref属性等来判断需要注入的到底是什么数据类型,并要对这些值进行合适的类型转换,所以Spring实际的处理过程更复杂。
由此可见,
构造注入就是通过<constructor-arg>驱动Spring执行有参数的构造器;
设值注入就是通过<property>.驱动Spring执行setter方法。
不管哪种注入,都需要为参数传入参数值,而Java类的成员变量可以是各种数据类型,除了基本类型值、字符串类型值等,还可以是其他Java实例,也可以是容器中的其他Bean实例,甚至是Java集合、数组等,所以Spring允许通过如下元素为setter方法、构造器的参数指定参数值。

  1. value
  2. ref
  3. bean
  4. list,set,map,props
    上面4种情况分别代表Bean类的4种类型的成员变量,下面详细介绍这4种情况。

7.5.2 容器中Bean的作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。 Spring支持如下6种作用域

作用域 描述
singleton 单例模式,在整个Spring IoC容器中, singleton作用域的Bean将只生成一个实例。
prototype 每次通过容器的getBean()方法获取prototype作用域的Bean时,都将产生一个新的Bean实例
request 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在web应用中使用Spring时,该作用域才真正有效。
session 对于一次HTTP会话,session作用域的Bean将只生成一个实例,这意味着,在同一次HTTP会话内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效.
application 对应整个Web应用,该Bean只生成一个实例。这意味着,在整个Web应用内,程序每次请求该Bean时,得到的总是同一个实例。只有在web应用中使用Spring时,该作用域才真正有效。
websocket 在整个WebSocket的通信过程中,该Bean只生成一个实例。只有在web应用中使用Spring时,该作用域才真正有效。

比较常用的是singletonprototype两种作用域,

  • 对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;
  • 如果一个Bean被设置成prototype作用域,程序每次请求该idBean, Spring都会新建一个Bean实例,然后返回给程序。在这种情况下, Spring容器仅仅使用new关键字创建Bean实例,一旦创建成功,容器就不再跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域, Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此, prototype作用域的Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,就可以重复使用。因此,应该尽量避免将Bean设置成prototype作用域。

通过scope属性执行Bean的作用域

Spring配置文件通过scope属性指定Bean的作用域,该属性可以接受singletonprototyperequestsessionglobalSession五个值,分别代表上面介绍的5个作用域。

程序示例

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

下面的配置文件中配置singleton作用域的Beanprototype作用域Bean各有一个。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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">
<!-- 配置一个singleton作用域Bean实例 -->
<bean id="p1" class="org.crazyit.app.service.Person" />
<!-- 配置一个prototype Bean实例 -->
<bean id="p2" class="org.crazyit.app.service.Person"
scope="prototype" />
<bean id="date" class="java.util.Date" />
</beans>

从上面的代码中可以看到,

  • 配置p1对象时没有指定scope属性,则它默认是一个singleton作用域的Bean;
  • p2则指定了scope="prototype"属性,这表明它是一个prototype作用域的Bean
  • 除此之外,上面配置文件还配置了一个iddatesingleton作用域的Bean

BeanTest.java

主程序通过如下代码来测试两个Bean的区别。

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

import org.springframework.context.*;
import org.springframework.context.support.*;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 以类加载路径下的beans.xml文件创建Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml"); // ①
// 判断两次请求singleton作用域的Bean实例是否相等
System.out.println(ctx.getBean("p1") == ctx.getBean("p1"));

// 判断两次请求prototype作用域的Bean实例是否相等
System.out.println(ctx.getBean("p2") == ctx.getBean("p2"));
System.out.println(ctx.getBean("date"));
Thread.sleep(1000);
System.out.println(ctx.getBean("date"));
}
}

程序执行结果如下:

1
2
3
4
true
false
Mon Aug 26 16:14:21 CST 2019
Mon Aug 26 16:14:21 CST 2019

从上面的运行结果可以看出,
对于singleton作用域的Bean,每次请求该idBean,都将返回同个共享实例,因而两次获取的Bean实例完全相同;
但对prototype作用域的Bean,每次请求该idBean都将产生新的实例,因此两次请求获得的Bean实例不相同。
上面程序的最后还分两次获取,并输出Spring容器中iddateBean代表的时间,虽然程序获取、输出两个date的时间相差一秒,但由于iddateBean是一个singleton Bean,该Bean会随着容器的初始化而初始化——也就是在①号代码处, date Bean已经被创建出来了,因此无论程序何时访问、输出date Bean所代表的时间,永远输出①号代码的执行时间。

通过singleton属性指定Bean的作用域

早期指定Bean的作用域也可通过singleton属性指定,该属性只接受两个属性值:truefalse,分别代表singletonprototype作用域。使用singleton属性则无法指定其他三个作用域,实际上Spring 2.x不推荐使用singleton属性指定Bean的作用域, singleton属性是Spring 1.2.x的方式

request和session作用域

对于request作用域,查看如下Bean定义:

1
<bean id="loginAction" class="org.crazyit.app.struts.LoginAction" scope="request"/>

针对每次HTTP请求,Spring容器会创建一个全新的idloginActionBean实例,且该loginAction Bean实例仅在当前HttpRequest内有效。因此,如果程序需要,完全可以自由更改Bean实例的内部状态;其他请求所获得的loginAction Bean实例无法感觉到这种内部状态的改变。当处理请求结束时, request作用域的Bean实例将被销毁。
requestsession作用域的Bean只对Web应用才真正有效。实际上通常只会将Web应用的控制器Bean指定成request作用域
session作用域与request作用域完全类似,区别在于:request作用域的Bean对于每次HTTP请求有效,而session作用域的Bean则对于每次Http Session有效。
requestsession作用域只在Web应用中才有效,并且必须在web应用中增加额外配置才会生效。为了让requestsession两个作用域生效,必须将HTTP请求对象绑定到为该请求提供服务的线程上,这使得具有requestsession作用域的Bean实例能够在后面的调用链中被访问到

如何使得request作用域生效

Web应用的web.xml文件中增加如下Listener配置,该Listener负责使request作用域生效:

1
2
3
4
5
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>

一旦在web.xml中增加了如上所示配置,程序就可以在Spring配置文件中使用requestsession作用域了。下面的配置文件配置了一个实现类为PersonBean实例,其作用域是request。配置文件代码如下。

程序示例2

1
2
3
4
5
6
7
8
9
<?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">
<!-- 指定使用request作用域 -->
<bean id="p" class="org.crazyit.app.service.Person"
scope="request" />
</beans>

这样Spring容器会为每次HTTP请求生成一个Person实例,当该请求响应结束时,该实例也随之消失。

Spring MVC默认支持request作用域

如果Web应用直接使用Spring MVC作为MVC框架,即用SpringDispatcherServletDispatcherPortlet来拦截所有用户请求,则无须这些额外的配置,因为SpringDispatcherServletDispatcherPortlet已经处理了所有和请求有关的状态处理。
接下来本示例使用一个简单的JSP脚本来测试该request作用域,该JSP脚本两次向Spring容器请求获取idpBean-当用户请求访问该页面时,由于在同一个页面内,因此可以看到Spring容器两次返回的是同一个Bean。该JSP脚本如下。

test.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page contentType="text/html; charset=GBK" language="java"
errorPage=""%>
<%@ page import="org.springframework.web.context.*"%>
<%@ page import="org.springframework.web.context.support.*"%>
<%@ page import="org.crazyit.app.service.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Spring Bean的作用域</title>
</head>
<body>
<%
// 获取Web应用初始化的Spring容器
WebApplicationContext ctx = WebApplicationContextUtils
.getWebApplicationContext(application);
// 两次获取容器中id为p的Bean
Person p1 = (Person) ctx.getBean("p");
Person p2 = (Person) ctx.getBean("p");
out.println((p1 == p2) + "<br/>");
out.println(p1);
%>
</body>
</html>

使用浏览器请求该页面,将可以看到浏览器中输出如下内容:

1
2
true
org.crazyit.app.service.Person@5b82005

如果读者再次刷该页面,将可以看到该页面依然输出true,但程序访问、输出的Person Bean不再是前一次请求得到的Bean
关于HttpRequestHttpSession的作用范围,请参看第2章中关于Web编程的介绍
Spring5不仅可以为Bean指定已经存在的6个作用域,还支持自定义作用域。关于自定义作用域的内容,请参看Spring官方文档等资料。

7.5.1 Bean的基本定义和Bean别名

beans元素

<beans>元素是Spring配置文件的根元素,该元素可以指定如下属性

<beans>元素的属性 描述
default-lazy-init 指定该<beans>元素下配置的所有Bean默认的延迟初始化行为
default-merge 指定该<beans>元素下配置的所有Bean默认的merge行为.
default- autowire 指定该<beans>元素下配置的所有Bean默认的自动装配行为
default-autowire-candidates 指定该<beans>元素下配置的所有Bean**默认是否作为自动装配的候选Bean**。
default-init-method 指定该<beans>元素下配置的所有Bean默认的初始化方法
default-destroy-method 指定该< beans.>元素下配置的所有Bean默认的回收方法

<beans>元素下所能指定的属性都可以在每个<bean>子元素中指定,只要将属性名去掉default即可。也就是bean元素上可以指定lazy-init属性,来配置该Bean的默认延迟初始化行为。
<beans>元素上指定和在<bean>上指定的区别是:

  • 如果为<bean>元素指定这些属性,只对特定Bean起作用;
  • 如果为<beans>元素指定这些属性,这些属性将会对<beans>包含的所有Bean都起作用。

当二者所指定的属性不一致时,<bean>下指定的属性会覆盖<beans>下指定的属性。

bean元素

<bean>元素是<beans>元素的子元素,<beans>元素可以包含多个<bean>子元素,每个<bean>子元素定义一个Bean,每个Bean对应Spring容器里的一个Java实例。

bean元素常用属性

定义Bean时,通常需要指定两个属性。

<bean>元素常用属性 描述
id 确定该Bean的唯一标识,容器对Bean的管理、访问,以及该Bean的依赖关系,都通过id属性完成Beanid属性在Spring容器中应该是唯一的.
class 指定该Bean的具体实现类,这里不能是接口。Spring容器必须知道创建Bean的实现类,而不能是接口。在通常情况下, Spring会直接使用new关键字创建该Bean的实例,因此,这里必须提供Bean实现类的类名。
在一些特殊的情况下, Spring会采用其他方式创建Bean实例,例如工厂方法等,则可能不再需要class属性。这些内容需要参考后面的介绍。

通过name属性在定义Bean是给Bean指定别名

id属性是容器中Bean的唯一标识,这个id属性必须遵循XML文档的i属性规则,因此有一些特殊要求,例如不能以"/"等特殊字符作为属性值。但在某些特殊的情况下,Bean的标识必须包含这些特殊符号,此时可以采用name属性,用于指定Bean的别名,通过访问Bean别名也可访问Bean实例

除了可以为<bean>元素指定一个id属性之外,还可以为<bean>元素指定name属性,用于为Bean实例指定别名

<bean>元素的id属性具有唯一性,而且是一个真正的XML ID属性,因此其他XML元素在引用该id时,可以利用XML解析器的验证功能。

id属性命名规范

由于XML规范规定了XML ID标识符必须由字母数字组成,且只能以字母开头

为什么要使用别名

在一些特殊的情况下(例如与Struts 1整合过程中),必须为某些Bean指定特殊标识名,此时就必须为控制器Bean指定别名。

在其他地方为一个已经存在的Bean实例指定别名

在一些极端的情况下,程序无法在定义Bean时就指定所有的别名,而是需要在其他地方为一个已经存在的Bean实例指定别名,则可使用<alias>元素来完成,该元素可指定如下两个属性。

<alias>元素的属性 描述
name 该属性指定一个Bean实例的标识名,表明将为该Bean实例指定别名。
alias 指定一个别名。

例如以下示例配置:

1
2
3
4
<!-- 面代码为该Bean指定三个别名:#abc,@123,abc* -->
<bean id="person" class="..." name="#abc,@123,abc*"/>
<alias name="person" alias="jack"/>
<alias name="jack" alias="jackee">
  • 上面第一行代码的name属性为该Bean指定了三个别名#abc@123abc* ,这些别名中包含了些特殊字符,由此可见,作为别名的字符可以很随意。
  • 上面配置的后两行代码则用于为已有的person这个Bean指定别名。