6.Spring容器中的Bean
6.1 Bean定义和Bean别名
对于开发者来说,开发者使用Spring做2件事:(1)开发Bean (2)配置Bean。
<beans.../>标签的属性:
default-lazy-init
default-merge
default-autowire
default-autowire-condidates
default-init-method
default-destroy-method
<beans.../>的定义对每一个bean都有效,<bean.../>对单个bean有效,如果这两者冲突,<bean.../>会覆盖<beans.../>的定义。<bean.../>也可以采用name属性,用于指定Bean的别名,通过访问Bean别名也可以访问Bean实例。
指定别名有2种方式:
(1)通过name属性指定:可以指定多个,使用逗号、冒号或者空格分隔多个别名;
(2)通过alias元素指定:一个元素是name,一个元素是alias。并且这个标签,不是在<bean.../>里,而是跟<bean.../>并列的。
新建一个测试类,AliasTest.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
|
package com.pmpa.ch03; public class AliasTest { private String name; private int age; 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; } @Override public String toString() { return "AliasTest [name=" + name + ", age=" + age + "]" ; } } |
配置文件:
1
2
3
4
5
|
< bean id = "aliastest" class = "com.pmpa.ch03.AliasTest" name = "#221,iuffs,googke" > < property name = "name" value = "honda" ></ property > < property name = "age" value = "13" ></ property > </ bean > < alias name = "googke" alias = "Sun" ></ alias > |
测试类BeanTest.java
1
2
|
AliasTest at = ctx.getBean( "Sun" , AliasTest. class ); System.out.println(at); |
运行结果:
1
|
AliasTest [name=honda, age=13] |
可以看到,通过别名“Sun”,Spring也找到了这个Bean,并正常输出。
6.2 容器中Bean的作用域
Bean包含5种作用域:
singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效
6.2.1 singleton和prototype作用域测试实例:
如果不指定Bean的作用域,默认是singleton。prototype作用域的Bean的创建、销毁的系统代价比较大,应尽量避免使用。Spring配置文件的<bean.../>标签通过scope属性来配置bean的作用域。
ScopeTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.pmpa.ch03; public class ScopeTest { private String idNo; public String getIdNo() { return idNo; } public void setIdNo(String idNo) { this .idNo = idNo; } @Override public String toString() { return "ScopeTest [idNo=" + idNo + "]" ; } } |
配置文件 beans03.xml
1
2
3
4
5
6
|
< bean id = "scope1" class = "com.pmpa.ch03.ScopeTest" scope = "prototype" > < property name = "idNo" value = "152301ABC" ></ property > </ bean > < bean id = "scope2" class = "com.pmpa.ch03.ScopeTest" scope = "singleton" > < property name = "idNo" value = "110332CDE" ></ property > </ bean > |
测试类BeanTest.java:
1
2
3
4
5
6
7
8
9
|
//prototype作用域Bean的测试: ScopeTest st_prototype_1 = ctx.getBean( "scope1" , ScopeTest. class ); ScopeTest st_prototype_2 = ctx.getBean( "scope1" , ScopeTest. class ); System.out.println( "prototype类型的对象1和2是否是同一对象:" + (st_prototype_1 == st_prototype_2)); //sigleton作用域Bean的测试: ScopeTest st_singleton_1 = ctx.getBean( "scope2" , ScopeTest. class ); ScopeTest st_singleton_2 = ctx.getBean( "scope2" , ScopeTest. class ); System.out.println( "sigleton类型的对象1和2是否是同一对象:" + (st_singleton_1 == st_singleton_2)); |
运行结果:
1
2
|
prototype类型的对象1和2是否是同一对象:false sigleton类型的对象1和2是否是同一对象:true |
对于singleton作用域的Bean,每次请求该id的bean,都将返回同一个共享实例。
6.2.2 request、session、globalsession作用域测试实例(待补充):
6.3 配置依赖
BeanFactory和ApplicationContext实例化容器中Bean的时机不同:前者等到程序需要Bean实例时才创建;后者在容器创建ApplicationContext实例时,会初始化容器中的所有singleton Bean。
ApplicationContext可以在容器初始化阶段检验出配置错误。
Spring允许通过如下元素为setter方法、构造器参数指定参数值。
value
ref
bean
list、set、map、props
以上4种情况分别代表Bean类的4种类型的成员变量。
6.3.1 value:
value元素用于指定基本类型及其包装、字符串类型的参数。前边讲述的内容中,包含很多value赋值的情形,这里不再赘述。
6.3.2 ref:
如果需要为Bean设置的属性值是容器中的另一个Bean实例,则应该使用<ref.../>元素(其实,常用的是ref属性,前边事例都是这样。)这种术语也叫作“注入合作者Bean”。ref实例在前边例子中也很多,不再赘述。
自动装配注入合作者Bean:
Spring能自动装配Bean与Bean之间的依赖关系,即无需使用ref显示指定依赖Bean,而是由Spring容器检查XML配置文件内容,根据某种规则,为调用者Bean注入被依赖的Bean。
自动装配通过<beans.../>的default-autowrite属性指定,也可以通过<bean.../>元素的autowire属性指定。
自动装配可以减少配置文件的工作量,但是降低了依赖关系的透明性和清晰性。
auto-wire属性的可用值:
no:不使用自动装配,这是默认配置。
byName:根据setter方法名进行自动装配。
byType:根据setter方法的形参类型来自动装配。如果找到多个和形参类型匹配的Bean,则抛出异常。
constructor:与byType类似,区别是用于自动匹配构造器的参数。
autodetect:Spring容器根据Bean内部结构,自行决定使用constructor或byType策略。
AutoWireTest.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
|
package com.pmpa.ch03; public class AutoWireTest { private String uName; private Car car; private House house; public String getuName() { return uName; } public void setuName(String uName) { this .uName = uName; } public Car getCar() { return car; } public void setCar(Car car) { this .car = car; } public House getHouse() { return house; } public void setHouse(House house) { this .house = house; } public void testAutoWire() { System.out.println( "begin testing autowire " + uName); car.carTest(); house.testHouse(); } } |
在外部,创建2个类,分别是Car和House,这2个类很简单。
配置文件beans03.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< bean id = "benz" class = "com.pmpa.ch03.Car" > < property name = "brand" value = "benz" ></ property > < property name = "speed" value = "270" ></ property > </ bean > < bean id = "biguiyuan" class = "com.pmpa.ch03.House" > < property name = "address" value = "GuoMao" ></ property > < property name = "price" value = "78900000" ></ property > </ bean > < bean id = "autowiretest" class = "com.pmpa.ch03.AutoWireTest" autowire = "byType" > < property name = "uName" value = "自动装配测试1" ></ property > </ bean > |
这时候,创建的2个bean实例benz和biguiyuan的类型分别是Car和House,所以如果autowiretest实例的autowire属性设置为byType,则可以自动装配这2个属性。
测试类BeanTest.java
1
2
|
AutoWireTest awt = ctx.getBean( "autowiretest" , AutoWireTest. class ); awt.testAutoWire(); |
运行结果:
1
2
3
|
begin testing autowire 自动装配测试1 The car benz can reach the speed of 270.0 The house GuoMao can reach the price of 7.89E7 |
通过byType自动装配成功。如果想通过byName完成自动装配,则需要修改配置文件,修改Car和House类型的2个bean实例的id(修改成什么Id,需要根据类AutoWireTest的2个setter方法来确定,应该是car和house),修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< bean id = "car" class = "com.pmpa.ch03.Car" > < property name = "brand" value = "benz" ></ property > < property name = "speed" value = "270" ></ property > </ bean > < bean id = "house" class = "com.pmpa.ch03.House" > < property name = "address" value = "GuoMao" ></ property > < property name = "price" value = "78900000" ></ property > </ bean > < bean id = "autowiretest" class = "com.pmpa.ch03.AutoWireTest" autowire = "byName" > < property name = "uName" value = "自动装配测试1" ></ property > </ bean > |
当一个Bean既使用自动装配依赖,又使用ref显示指定依赖时,则显示指定的依赖会覆盖自动装配的依赖。
6.3.3 注入嵌套Bean:
如果某个Bean所依赖的Bean不想被Spring容器直接访问,可以使用嵌套Bean。
把<bean.../>配置成<property.../>或者<constructor-args.../>的子元素,那么该<bean.../>元素配置的Bean仅仅作为setter注入、构造注入的参数,这种Bean就是嵌套Bean。由于容器不能获取嵌套Bean,因此不需要为嵌套Bean指定id元素。
注意如下的配置文件:
1
2
3
4
5
|
< bean id = "chinese02" class = "com.pmpa.ch02.Chinese" > < property name = "axe" > < bean class = "com.pmpa.ch02.SteelAxe" ></ bean > </ property > </ bean > |
使用嵌套Bean与使用ref引用容器中另一个Bean在本质上是一样的。 之前的写法是<property name="..." ref="..."/>,现在改成<property name="..."><bean class="..."/></property>。注意这种写法中,不能指定bean id属性。
6.3.4 注入集合值:
如果需要调用形参类型为集合的setter方法,或者调用形参类型为集合的构造器,则可使用集合元素<list.../>、<set.../>、<map.../>、<props.../>分别来设置类型为List、Set、Map和Properties的集合参数值。
下面定义一个包含集合属性的java类,Student.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
|
package com.pmpa.ch03; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class Student { private String name; private List<String> schools; private Map scores; private Map<String,Car> carPhases; private Properties examinations; private Set infos; private String[] careers; public String getName() { return name; } public void setName(String name) { this .name = name; } public List<String> getSchools() { return schools; } public void setSchools(List<String> schools) { this .schools = schools; } public Map getScores() { return scores; } public void setScores(Map scores) { this .scores = scores; } public Map<String, Car> getCarPhases() { return carPhases; } public void setCarPhases(Map<String, Car> carPhases) { this .carPhases = carPhases; } public Properties getExaminations() { return examinations; } public void setExaminations(Properties examinations) { this .examinations = examinations; } public Set getInfos() { return infos; } public void setInfos(Set infos) { this .infos = infos; } public String[] getCareers() { return careers; } public void setCareers(String[] careers) { this .careers = careers; } @Override public String toString() { return "Student [name=" + name + ", schools=" + schools + ", scores=" + scores + ", carPhases=" + carPhases + ", examinations=" + examinations + ", infos=" + infos + ", careers=" + Arrays.toString(careers) + "]" ; } } |
分别为<property.../>元素增加<list.../>、<set.../>、<map.../>、<props.../>子元素来配置这些集合类型的参数值。
配置文件定义bean03.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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
< bean id = "toyota" class = "com.pmpa.ch03.Car" > < property name = "brand" value = "toyota" ></ property > < property name = "speed" value = "180" ></ property > </ bean > < bean id = "bmw" class = "com.pmpa.ch03.Car" > < property name = "brand" value = "bmw" ></ property > < property name = "speed" value = "270" ></ property > </ bean > < bean id = "bently" class = "com.pmpa.ch03.Car" > < property name = "brand" value = "bently" ></ property > < property name = "speed" value = "460" ></ property > </ bean > < bean id = "student" class = "com.pmpa.ch03.Student" > < property name = "name" value = "Tom White" ></ property > <!-- 配置List集合类型 --> < property name = "schools" > < list > <!-- 下面每个value标签,定义一个list的元素,根据list泛型的类型,也可以使用ref、bean等等标签 --> < value >衡水一中</ value > < value >北京工业大学</ value > < value >斯坦福大学</ value > </ list > </ property > <!-- 配置Map集合类型 --> < property name = "scores" > < map > <!-- 下面每个entry标签,定义一个map的元素,也就是key-value对 --> < entry key = "数学" value = "78" ></ entry > < entry key = "语文" value = "132" ></ entry > < entry key = "英语" value = "104" ></ entry > </ map > </ property > <!-- 配置Map集合类型,Map<String,Car> --> < property name = "carPhases" > < map > <!-- 下面每个entry标签,定义一个map的元素,也就是key-value对 --> < entry key = "打工阶段" value-ref = "toyota" ></ entry > < entry key = "创业阶段" value-ref = "bmw" ></ entry > < entry key = "成功阶段" value-ref = "bently" ></ entry > </ map > </ property > <!-- 配置Properties集合属性 --> < property name = "examinations" > < props > <!-- 每个prop元素都配置一个属性项,其中key指定属性名 --> < prop key = "血常规" >正常</ prop > < prop key = "尿常规" >脂肪肝</ prop > </ props > </ property > <!-- 配置Set集合属性 --> < property name = "infos" > < set > <!-- 每个value、ref、bean都配置一个Set元素 --> < value >简单的字符串赋值</ value > < bean class = "com.pmpa.ch03.House" ></ bean > < ref bean = "car" ></ ref > <!-- Set里可以继续配置一个List --> < list > < value >Set中的List</ value > < set >< value type = "java.lang.String" >List中的Set</ value ></ set > </ list > </ set > </ property > <!-- 配置数组集合类型 --> < property name = "careers" > < list > <!-- 每个value标签是一个数组元素 --> < value >IBM</ value > < value >Google</ value > < value >Oracle</ value > </ list > </ property > </ bean > |
测试类BeanTest.java
1
2
3
|
System.out.println( "-----------------------测试集合注入------------------------" ); Student student = ctx.getBean( "student" , Student. class ); System.out.println(student); |
运行结果:
1
2
|
-----------------------测试集合注入------------------------ Student [name=Tom White, schools=[衡水一中, 北京工业大学, 斯坦福大学], scores={数学=78, 语文=132, 英语=104}, carPhases={打工阶段=Car [brand=toyota, speed=180.0], 创业阶段=Car [brand=bmw, speed=270.0], 成功阶段=Car [brand=bently, speed=460.0]}, examinations={血常规=正常, 尿常规=脂肪肝}, infos=[简单的字符串赋值, com.pmpa.ch03.House@6d7b4f4c, Car [brand=benz, speed=270.0], [Set中的List, [List中的Set]]], careers=[IBM, Google, Oracle]] |
说明:
-
Spring对List元素和数组的处理是一致的,都使用<list.../>元素配置。
-
<list.../>、<set.../>、<map.../>、<props.../>又可以接受value;ref;bean;list、set、map、props元素。
-
Properties类型,其key和value只能是字符串。
-
<entry.../>元素支持4个属性:key、key-ref(key为容器中的其他bean实例)、value、value-ref(value为容器中的其他bean实例,上边例子中有说明)。
-
Spring容器支持集合的合并,子Bean中的结合属性值可以从其父Bean的集合属性继承和覆盖而来。
6.3.4 组合属性:
使用car.brand 等类似格式为对象成员变量的属性赋值。为Bean的组合属性设置参数时,除最后一个属性外,其他属性值都不允许为null。
CombinationTest.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 com.pmpa.ch03; public class CombinationTest { private String name; private Car car = new Car(); private House house = new House(); public String getName() { return name; } public void setName(String name) { this .name = name; } public Car getCar() { return car; } public void setCar(Car car) { this .car = car; } public House getHouse() { return house; } public void setHouse(House house) { this .house = house; } @Override public String toString() { return "CombinationTest [name=" + name + ", car=" + car + ", house=" + house + "]" ; } } |
配置文件:
1
2
3
4
5
6
|
< bean id = "combinationtest" class = "com.pmpa.ch03.CombinationTest" > < property name = "name" value = "combination" /> < property name = "car.brand" value = "honda" ></ property > < property name = "car.speed" value = "193" ></ property > < property name = "house.address" value = "XiDan" ></ property > </ bean > |
运行结果:
1
2
|
-----------------------测试组合属性------------------------ CombinationTest [name=combination, car=Car [brand=honda, speed=193.0], house=House [address=XiDan, price=0.0]] |
使用组合属性指定参数值时,除了最后一个属性外,其他属性都不能为Null,否则将引发NullPointerException异常,如果感觉自学困难的话,可以去参加java培训来提高。例如,上面配置文件为car.brand指定参数值,则CombinationTest的car就一定不能为null,所以该类在设置private属性时,要直接new一个对象。是这样:private Car car = new Car();而不能是这样:private Car car;
这种赋值方法,底层spring执行时,需要先调用getter方法,再调用setter方法,
1
|
combinationtest.getCar().setBrand( "honda" ); |
也就是说组合属性只有最后一个属性才调用setter方法,其余都调用getter方法——这也是为什么前面属性都不能为null的缘由。