

3. 3
依赖
你的典型的企业应用不会只有单一的对象(
spring
中叫做
bean
)组成。即使是最简单的应用也至少会有一系列的对象组成,它们共同协作、装配成面向用户的统一应用。
下面的部分将会介绍你如何组装单独的定义的
bean
来形成一个完整的应用(应用通常都是为了实现最终用户的某个特定目标)。
3.3.1
依赖注射
依赖注射(
DI
)的基本原则是:对象在从构造器或者工厂方法返回时,通过构造器参数、工厂方法参数或者对象实例的属性设置来定义它们的依赖性(如那些它协同的对象)。然后,在创建
bean
的时候,容器完成依赖注射工作。这就是所谓的控制反转(
Inversion of Control-IoC
):
bean
自身控制初始化,或使用它自身的构造器来定位自身的依赖,这有点类似于
Service Locator
模式。
什么明显,当
DI
原则被充分应用的时候,代码将会变得更干净(
cleaner
),同时应用实现了高层次的分离也会变得更容易,此时
bean
并不能感知它的依赖,但这些依赖却能够被提供给它们(
bean
甚至并不知道依赖的对象在哪儿,或者依赖的对象是什么类)。
正如前面所述,
DI
有两种主要的实现方式,分别叫做:
Setter Injection
和
Constructor Injection
。
3.3.1
.1 Setter Injection
(注射)
在调用一个无参数的构造器或者静态工厂方法创建
bean
之后,通过调用
bean
的
setter
方法来初始化
bean
的方法就叫做
setter injection
。
下面的例子中,该类只能用
setter
方法实现依赖注射。注意,该类只是一个平常的
Java
对象。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can 'inject' a MovieFinder
public void setMoveFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually 'uses' the injected MovieFinder is omitted...
}
3.3.1
.2 Constructor Injection
(构造器注射)
构造器注射方式是通过调用一个采用参数(参数表示协作或者依赖的事物)的构造器来实现的。此外,通过调用带有特定参数的静态工厂方法来创建
bean
的方式也可以看作是这种方法的延伸,下面的章节也把这两种情况类似看待。
下面的例子中,该类只能用构造器注射方法实现依赖注射。再次注意,该类也是一个普通类。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can 'inject' a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually 'uses' the injected MovieFinder is omitted...
}
选择constructor-
还是setter
?
Spring团队推荐使用setter方法,因为如果constructor的参数很多的话,就会变得很难处理,特别是当一些参数是可选的时候。Setter方法的出现使你能够在bean生成以后可以重新配置(重新注射)依赖。(JMX MBeans的管理就是一个典型的例子)。
经过如此,constructor方法还是会被一些纯化论者所推荐。他们的理由是:在bean初始化的时候就将bean的所有依赖注射完成意味着该对象可以尽量少的在客户端调用代码中出现。问题是:不能重新配置(注射)这个bean。
这儿并不存在硬性规定。无论哪种DI方式要依赖于具体的类;有时候,当你使用第三方的类的时候,你并没有类的具体实现细节,也并未开放任何的setter方法,那么此时的选择已经确定,那就是constructor方式。
|
BeanFactory
支持上面两种方式的注射方法。(实际上,如果部分依赖已经通过构造器方法注射后,它然后支持通过
setter
方法注射)依赖的配置在容器中表现为带有
PropertyEditor
实例的
BeanDefinition
,它知道如何变换属性的格式。然而,
Spring
的大多数用户一般不会直接处理这些类(例如通过程序方式),他们宁愿去处理将被转换成类实例的
XML
定义文件,然后加载整个
Spring IoC
容器。
Bean
依赖的处理方式通常有以下几种:
l
BeanFactory
通常在初始化的时候,会读取所有
bean
的配置文件。(大多数
Spring
用户使用
BeanFactory
或者
ApplicationContext
)
l
Bean
使用属性、构造器参数或者静态工厂方法参数来表示依赖。在创建的时候,这些依赖会传递给
bean
。
l
每个属性或者构造器参数要么是要设置的值,要么是对于容器中另外的
bean
的引用。
l
每个属性或者构造器参数能够被转换成实际要求的类型。默认的,
Spring
可以将
String
类型的值转换成内建类型,如
int
,
long
,
String
,
boolean
等。
认识到
Spring
实际上是在容器创建的时候来验证每个
Bean
的配置信息这点是十分重要的,包括
Bean
属性引用是引用合法的
bean
的验证。(如,被引用的
bean
要求是定义在同一个容器中。然而,
Bean
属性的设置是直到
bean
创建完成的时候才完成的。对于那些单例模式且被设置成预初始化的
bean
,是在容器初始化的时候就创建的,而其他的
bean
实际上是在被请求的时候才创建的。当
bean
被创建的时候,这会引起具有依赖的
bean
被创建,包括依赖的、依赖的依赖的等也被一起创建)。
循环依赖:
如果使用典型的构造器注射方式,有可能会形成循环依赖的情况。
考虑如下情况:类A在构造器注射时,需要类B实例,而类B同样需要A的实例。如果你配置A和B互相注射,那么Spring IoC容器会在运行时检测到循环引用,然后抛出BeanCurrentlyInCreationException异常。
解决此问题的一个方法是用setter方法代替构造器方法。另外一个解决方法,是不要使用构造器方法,而是使用setter。
|
你一般情况下可以相信
Spring
会做正确的事情。
Spring
会在容器加载的时候,监测不匹配的配置项,例如对于不存在
bean
的引用或者循环依赖。容器将尽可能晚的设置属性和解析依赖(如仅在请求发生的时候才会去创建依赖)。这意味着
Spring
在你请求一个
bean
(如果该
bean
创建错误或者创建依赖的时候错误)的时候,会比较晚的抛出异常。当一个
bean
在发现非法属性的时候就会抛出异常,这也引起容器抛出异常。某些配置项的晚邦定正是
ApplicationContext
接口默认预实例化单例
bean
的原因。
ApplicationContext
创建的同时,相关的配置项也一起创建,而这是以创建那些暂时不需要的
bean
所耗费的时间和内存为代价的。当然,你可以去重载这种默认行为,如将单例
bean
设置成“懒初始化(
lazy-initialize
)”(如:并不预初始化)。
最后,要说一下的是,在一个或者多个
bean
被注射到一个依赖的
bean
的时候,每个被注射的
bean
在注射之前就是配置完成的(通过
DI
方式)。也就是说,如果
bean A
对于
bean B
依赖,那么
Spring IoC
容器在调用
A
的
Setter
方法注射
B
之前,就完整配置好
B
;‘完整配置(
totally configure
)’的意思是
bean
被初始化(如果不是预实例化的单例
bean
的话),
bean
的所有依赖被设置并且相关的生命周期方法会被调用。(如配置初始化方法
-configured init method
或者初始化
bean
回调方法
-initializingbean callback method
)。
3.3.1
.3
例子
首先,第一个例子采用
XML
配置方式的
DI
。参看下面的部分的
bean
定义文件。
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested <ref/> element -->
<property name="beanOne"><ref bean="anotherExampleBean"/></property>
<!-- setter injection using the neater 'ref' attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
可以看出,针对
XML
配置文件中的属性,已经声明了相应的
setters
方法。
下面,另外一个例子是使用构造器注射方式。参看下面的配置片断和相应的
Java
类代码。
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested <ref/> element -->
<constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
<!-- constructor injection using the neater 'ref' attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
可以看出,
bean
定义中的构造器参数指定了将会传递给
ExampleBean
构造器的参数值。
下面,考虑构造器方法的一个变形的情况,
Spring
通过调用静态工厂方法来返回对象实例。
<bean id="exampleBean" class="examples.ExampleBean"
factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations
...
return eb;
}
}
注意,传递给静态工厂方法的参数是用
constructor-arg
元素实现的,这点和构造器方法是一样的。另外重要的一点是,通过工厂方法返回的类并不一定就是包含了工厂方法的类,尽管本例子是这样。实例工厂方法和这类似(不同的是用
factory-bean
属性代替
class
属性),细节这里不再赘述。
3.3.2
构造器参数解析
当使用参数类型时,构造器参数解析匹配将会发生。如果在
bean
定义中,不存在参数歧义的情况,那么
bean
定义文件的构造器参数顺序就是实际传递给
bean
构造器的参数顺序。参看下面的类:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
此时,不存在潜在的歧义情况(假定类
Bar
和类
Baz
无继承层次关联)。这样,下面的配置将会正常工作,并且也不需要指定构造器参数的索引,也不需要显式指定参数类型,它会按照你期望的那样正常工作。
<beans>
<bean name="foo" class="x.y.Foo">
<constructor-arg>
<bean class="x.y.Bar"/>
</constructor-arg>
<constructor-arg>
<bean class="x.y.Baz"/>
</constructor-arg>
</bean>
</beans>
当
bean
被引用的时候,可以知道类型,并且发生匹配(正如前面的例子那样)。然而,在使用简单值类型,如
<value>true<value>
的时候,
Spring
无法确定值的类型,此时如果没有其他配置的话,将无法正确匹配。参考下面的类,后面将会使用。
package examples;
public class ExampleBean {
// No. of years to the calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
3.3.2
.1
构造器参数类型匹配
对于上面的情况可以通过使用
'type'
属性指定构造器参数类型,从而实现简单类型匹配。如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
3.3.2
.2
构造器参数索引
可以通过使用
index
属性来显式的制定构造器参数的索引。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
在一个构造器有多个同类型参数的情况下,通过指定参数索引,同样可以解决多个简单值类型参数的歧义问题。注意,索引从
0
开始。

指定构造器参数索引是
IoC
容器的首选方式。
3.3.3
Bean
属性和构造器参数细节
正如前面所述,
bean
属性和构造器参数可以引用其他受管理的
bean
(协作者),或者是内部定义的值。
Spring
的
XML
配置中的
<property/>
和
<constructor-arg/>
属性有很多子元素就可以实现这点。
3.3.3
.1
直接赋值(原始类型,串型等)
<value/>
属性以易于阅读的方式来制定属性或者构造器参数的值。正如前面所述,
JavaBean
的
PropertyEditors
将这些值从
java.lang.String
类型转换成相应的类型。
<bean id="myDataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/mydb</value>
</property>
<property name="username">
<value>root</value>
</property>
</bean>
3.3.3
.1.1 idref
元素
Idref
元素是一种简单的传递容器中的其他的
bean
的
id
的“错误验证”(
error-proof
)方式。(
<constructor-arg/>
或者
<property/>
元素的子元素)
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean" />
</property>
</bean>
上面的bean定义文件等同于下面的定义:
<bean id="theTargetBean" class="..."/>
<bean id="client" class="...">
<property name="targetName">
<value>theTargetBean</value>
</property>
</bean>
第一种方式比第二种更好,原因是idref标志让容器在分发时验证被引用的bean是否实际存在。第二种情况中,并没用验证
'targetName'
属性引用的
bean
是否存在。
对于第二种方式,在被引用
bean
被实例化之前,例外将会随时可能抛出(往往会导致系统崩溃)。如果被引用的是原型
bean
,那么,那么异常可能会在容器分发完毕之后很长时间才会抛出。
此外,如果被引用的
bean
是在同样的
XML
配置单元,且
bean
的
name
就是
bean
的
id
,可以使用
'local'
属性来指定,这样,在
XML
文档解析的时候,就可以验证
bean
的合法性。
<property name="targetName">
<!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown -->
<idref local="theTargetBean"/>
</property>
3.3.3
.2
对于其他
bean
的引用(协作者)
Ref
是最后一个可以包含在
<constructor-arg/>
或者
<property/>
中的元素。它被用来指定引用其它的
bean
的属性值(协助者)。正如前面所述,被引用的
bean
被认为是引用它的
bean
的依赖,会在属性设置之前被实例化(对于单例
bean
可能已经完成了初始化)。
所有的引用最终只是对于另外一个对象的引用,但是有三种方法来指定被引用对象的
id
或者
name
,不同的方法决定对象的范围和验证方式的不同。
通过使用
<ref/>
标签的
bean
属性来制定目标
bean
是最常用的方式,这将会创建一个对于同容器或者父容器的
bean
的引用(无论在不在同样的
XML
文件中)。
'bean'
属性的值可以是目标
bean
的
'id'
属性,或者是
'name'
属性。
<ref bean="someBean"/>
通过
local
属性来制定目标
bean
的方式,可以利用
XML
解析器验证同一文件中的
XML
的
ID
引用的功能。
Local
属性必须和目标
bean
的
id
属性值相同。如果没有匹配到对应的元素,那么
XML
解析器将会抛出异常。因此,如果目标
bean
在同一个定义文件中,那么采用
local
属性是最好的方式(可以尽可能早的发现错误)。
<ref local="someBean"/>
通过
'parent'
属性来制定目标
bean
,可以引用当前容器的父容器中的
bean
。
'parent'
的值可以是目标
bean
的
id
属性,或者
'name'
属性值,且目标
bean
必须在当前容器的父容器中。当存在容器继承情况,而且你需要封装一个父容器中的
bean
,且需要实现和父
bean
的同名代理时(例如,子
bean
的定义将会重载父
bean
的定义)。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of the 'parent' bean
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <-- notice how we refer to the parent bean
</property>
<!-- insert other configuration and dependencies as required as here -->
</bean>
(坦率的说,
'parent'
属性较少使用。
)
3.3.3
.3
内部
bean
<property/>
或者
<constructor-arg>
属性的
<bean/>
元素用来定义叫做内部
bean
的
bean
(
inner bean
)。内部
bean
的定义不需要
id
或者
name
,由于
id
和
name
将会被容器忽略,所以不指定任何
id
或者
name
就是最好的方式。
参看下面的一个内部
bean
的例子:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target inline -->
<property name="target">
<bean class="com.mycompany.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
注意,在内部
bean
的情况下,
'singleton'
,
'id'
或者
'name'
属性将会被容器完全忽略。内部
bean
总是以匿名方式存在,且它们总是原型
bean
。请记住,试图将内部
bean
注射到依赖的
bean
中是不可能的。
3.3.3
.4
集合
<list/>
,
<set/>
,
<map/>
和
<props/>
元素可以定义和设置
Java
集合类型的属性和构造器参数,如:
List
,
Set
,
Map
和
Properties
。
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@somecompany.org</prop>
<prop key="support">support@somecompany.org</prop>
<prop key="development">development@somecompany.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry>
<key>
<value>yup an entry</value>
</key>
<value>just some string</value>
</entry>
<entry>
<key>
<value>yup a ref</value>
</key>
<ref bean="myDataSource" />
</entry>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
注意,
map
的
key
或者
value
的值,可以使下面的元素中的一种:
bean | ref | idref | list | set | map | props | value | null
3.3.3
.4.1
集合合并
从
Spring2.0
开始,容器也支持集合的合并。应用开发者可以定义一个父类型的
<list/>
,
<map/>
,
<set/>
或者
<props/>
元素,然后定义子类型的
<list/>
,
<map/>
,
<set/>
或者
<props/>
元素,以实现对于父集合值的继承和重载;子集合的值是父、子集合的值合并的结果,子集合元素重载对应的父集合的值。
请注意,合并的定义使用的是父子
bean
机制(
parent-child
)。该概念还仍然没介绍,所以不熟悉的读者在继续之前,需要阅读一下相关的章节。(参看第
3.6
章,“
bean
定义继承”)。
下面的例子能够很好的演示这个特性:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@somecompany.com</prop>
<prop key="support">support@somecompany.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the *child* collection definition -->
<props merge="true">
<prop key="sales">sales@somecompany.com</prop>
<prop key="support">support@somecompany.co.uk</prop>
</props>
</property>
</bean>
<beans>
请注意
child bean
的定义中,
adminEmails
属性的
<props/>
元素中,
merge
属性设置为
true
。当
child bean
实际在容器中实例化的后,实例将会拥有父、子
bean
合并后的
adminEmails
属性集合。
administrator=administrator@somecompany.com
sales=sales@somecompany.com
support=support@somecompany.co.uk
注意,子属性集合的值继承了所有父属性集合的元素值,同时,子属性集合中的
support
值重载了父集合中的值。
合并行为可以类似的应用与
<list/>
,
<map/>
,
和
<set/>
集合类型。对于
<list/>
元素,与
List
集合类型(例如一个有序集合值)关联的语义会被维护,父值始终前导子值。对于
Map
,
Set
和
Properties
集合类型,并不是有序的,因此,对于
Map
,
Set
和
Properties
集合的实现类型,容器并不维护有序的语义。
最后,再说明几个关于合并的问题;不可以合并不同类型集合(如不能合并
Map
和
List
),因此,如果你尝试那么做,那么一个异常将被抛出;需要明确的一点是,
'merge'
属性必须在“低层次”(
lower level
)设置,并且是继承型且子定义型的;如果在父集合定义中指定
'merge'
属性,那么并不会产生期望的合并;最后,请注意合并特性只有在
Spring2.0
中可以使用。(或者以后的版本)
3.3.3
.4.2
强类型集合(仅对于
Java5+
)
如果你有幸成为
Java5
(
Tiger
)用户之一,你将会了解到它是支持强类型集合的(我推荐的)。也就是说,可以声明一个只包含
String
元素的
Collection
类型。
如果你利用
Spring
将强类型的
Collection
注射到
bean
,那么你可以利用
Spring
的类型转化(
type-conversion
)支持,它在元素被添加到
Collection
之前,将强类型
Collection
实例的元素转化为合适的类型。
下面的例子可以清晰地说明这点;参看下面的类定义和它的
XML
配置内容
...
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当注射
'foo'bean
的
'accounts'
属性的时候,可以通过反射机制来获取强类型集合
Map<String,Float>
的元素类型信息,所以
Spring
的类型转换机制将会把值看作是
Float
类型,因此
'9.99', '2.75'
,
和
'3.99'
将被转换成实际的
Float
类型。
3.3.3
.5
空类型
<null/>
元素被用来处理空值。
Spring
把赋予属性等的空参数视为空串。下面的
XML
配置段演示了如何将
email
属性置为空的串值(
””
)。
<bean class="ExampleBean">
<property name="email"><value></value></property>
</bean>
上面的配置等同于下面的
Java
代码:
exampleBean.setEmail(“”)
。指定的
<null>
元素也可以被用来指定空值。例如:
<bean class="ExampleBean">
<property name="email"><null/></property>
</bean>
上面的配置等同于下面的
Java
代码:
exampleBean.setEmail(null)
。
3.3.3
.6 XML
配置方式的捷径
需要配置一个值或者
bean
引用的情况很多见,
Spring
中有一些比使用
<value/>
或者
<ref/>
元素更便捷的方式。
<property/>
,
<constructor-arg/>
,
和
<entry/>
元素均支持
’value’
属性,这可以用来替代内嵌的
<value/>
元素。参看下面的例子:
<property name="myProperty">
<value>hello</value>
</property>
<constructor-arg>
<value>hello</value>
</constructor-arg>
<entry key="myKey">
<value>hello</value>
</entry>
等同于:
<property name="myProperty" value="hello"/>
<constructor-arg value="hello"/>
<entry key="myKey" value="hello"/>
一般来说,在手工写配置的时候,建议你使用便捷的方式(
Spring
团队也是这么做的)。
<property/>
和
<constructor-arg/>
元素支持
'ref'
属性,可以用来代替内嵌的
<ref/>
元素。参看下面的例子:
<property name="myProperty">
<ref bean="myBean">
</property>
<constructor-arg>
<ref bean="myBean">
</constructor-arg>
等同于:
<property name="myProperty" ref="myBean"/>
<constructor-arg ref="myBean"/>
注意,这种方式是等同于
<ref bean=”xxx”>
配置的;对于
<ref local=”xxx”>
并没有类似的便捷方式。为了实现强制的本地引用,你必须使用原来的配置方式。
最后,
entry
元素也支持制定
key
值或者
map
值得便捷方式,它使用
’key’
,
’key-ref’
和
’value’
,
’value-ref’
属性来实现。参看下面的例子:
<entry>
<key>
<ref bean="myKeyBean" />
</key>
<ref bean="myValueBean" />
</entry>
等同于:
<entry key-ref="myKeyBean" value-ref="myValueBean"/>
同样,这种方式是等同于
<ref bean=”xxx”>
配置的;对于
<ref local=”xxx”>
并没有类似的便捷方式。
3.3.3
.7
复合属性的命名
在设置
bean
属性的时候,复合或者内嵌的属性命名是合法的,只要所有在路径上除了最后属性都是非空。例如:
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
Foo
这个
bean
有一个叫做
fred
的属性,而
fred
则有一个叫做
bob
的属性,同时
bob
则有一个叫做
sammy
的属性,最后的这个属性
sammy
被设置为
123
。
Foo
的
fred
属性,
fred
的
bob
属性必须在
bean
被构造后保持非空,否则
NullPointerException
异常将被抛出。
3.3.4
使用
depends-on
大多数情况下,
bean
的依赖可以简单的通过设置属性来实现,通常利用
XML
配置的
<ref/>
元素来实现。另外一种方式是,
bean
会被赋予依赖的对象
ID
(使用串值或者
<idref/>
元素)。第一方式,
bean
以程序方式向容器请求它的依赖对象。而第二种,在依赖产生之前,被依赖的对象实际上已经被实例化。
有些情况下,
bean
之间的依赖相对不是很直接(例如,数据库驱动注册,此时,一个静态的类初始化方法需要被触发),那么可以使用
'depends-on'
属性来在
bean
被使用之前,显式的触发它的实例化。参看下面的例子:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
如果需要表示多个
bean
的依赖,可以用逗号,空格或者分号等所有合法的分隔符将
'depends-on'
属性的值隔开,参看下面的例子,它演示了如何配置对于多个
bean
的依赖。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
3.3.5
懒实例化
bean
ApplicationContext
的默认行为是在启动的时候,尽可能早的预实例化所有的单例
bean
。所谓“预实例化”是指
ApplicationContext
实例会在自身的初始化过程中,尽可能的创建和配置所有的单例
bean
。通常这是正确的,因为这意味着在配置中,或者在相关辅助环境中的错误会被立刻发现(相比可能会在数小时甚至数天才被发现而言)。
然后,有时这种默认的行为却不是我们所需要的。当你不需要
ApplicationContext
预实例化单例
bean
时,你可以(在
bean
定义文件的基础上)有选择地控制这种默认行为,将
bean
设置为懒实例化(
lazy-initialized
)。懒实例化
bean
告诉容器实在启动时创建,还是当被请求的时候才创建。
通过
XML
配置
bean
时,是通过
'lazy-init'
属性来控制“懒加载”(
lazy-loading
)的。参看下面的例子:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true">
<!-- various properties here... -->
</bean>
<bean name="not.lazy" class="com.foo.AnotherBean">
<!-- various properties here... -->
</bean>
对于上面的配置,叫做
’lazy’
的
bean
在
ApplicationContext
初始化时不会被预实例化,而叫做
’not.lazy’
的
bean
则会被尽早的预实例化。
关于懒实例化,需要说明的一点是,即使
bean
被配置成懒实例化,假如该
bean
是一个非懒实例化的
bean
的依赖,那么在
ApplicationContext
预实例化
bean
的时候,会创建它的所有依赖,即使它们中存在懒实例化的
bean
!所以,当容器把你配置成懒实例化的
bean
实例化的时候,不必疑惑;那是因为,这个懒实例化的
bean
是被依赖注射到了你的配置中其他某个地方的非懒实例化的单例
bean
。
在容器层次,还可以通过使用
<beans/>
元素的
'default-lazy-init'
属性实现懒实例化;参看下面的例子:
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
3.3.6
自装配的协作者
Spring
的
IoC
容器可以自装配
Bean
之间的协作关系。这意味着,可以让
Spring
通过检查
BeanFactory
的内容,来解析
bean
的协作关系。自装配有
5
种模式。自装配是针对
bean
定义的,因此,可以指定一些
bean
自装配,而另外一些则不需要。使用自装配模式,完全可能不需要指定属性或者构造器参数,这样可以少一些手工配置。在
XML
配置中,自装配模式通过使用
<bean/>
的
autowire
属性来实现。参看,
autowire
允许的值列表:
表
3.2.
自装配模式
模式
|
解释
|
no
|
不使用自装配模式。
Bean
的引用使用
ref
元素定义。这是默认方式,对于大多数开发者来说,建议不要修改这个默认配置,因为,显式的指定协作者的方式有更多的可控性和明确性。从某种意义上说,这是系统构造的标准方式。
|
byName
|
属性名方式自装配。容器会寻找和属性同名的
bean
来进行装配。例如,假设有一个
bean
被设置成这种方式,它包含一个
master
属性(也就是说,它有一个说它
Master(...)
方法),
Spring
会查询命名为
master
的
bean
,然后用它来设置属性。
|
byType
|
属性类型方式自装配。这种方式假定在容器中存在一个和属性类型相同的
bean
。如果在容器中有两个这样的
bean
存在,那么将会抛出致命异常,这种情况下是不允许使用这种方式的。如果,没有这样的
bean
,那么没有任何问题;该属性不被设置。如果需要有提示,那么将
dependency-check
属性设置为
”objects”
,那么这种情况下,将会抛出一个错误。
|
constructor
|
类似于
byType
方式,但是针对的是构造器参数。如果在容器中,不存在与构造器参数类型相同的
bean
,致命异常被抛出。
|
autodetect
|
通过
bean
类自己来选择
constructor
还是
byType
方式。如果
bean
采用的是默认的构造器,那么将会使用
byType
方式。
|
注意,对于
property
和
constructor-arg
的显式依赖设置会覆盖自装配模式。也要注意一点的是,自装配模式不适用与简单类型属性,如原始类型、
Strings
和
Classes
类型(包括这些简单类型组成的数组类型)。(尽管这是应该被设计和考虑实现的一个特性)自装配模式应该和依赖检查结合起来,这样,在装配完成后,可以进行进一步的检查。
了解自装配模式的优劣也是很重要的。
下面是优点:
l
自装配模式可以大大减少手工配置内容。然而,在这方面,诸如
bean
模版等机制也可以做到(在后面讨论)。
l
自装配模式在对象发生更新的时候,可以自动的更新配置情况。例如,当需要为一个类增加额外的依赖时,那么通过自装配可以在不修改配置信息的情况下实现。因此,在开发期间,自装配模式什么有用,当代码基线逐步稳定以下,可以在转换成显式配置方式。
下面是缺点:
l
自装配模式相比而言更加具有不可确定性。尽管,在上面的表格中提到,
Spring
尽可能的避免因为猜测而导致不可预知的结果,但是
Spring
管理的那些对象之间的关系变得不再明确。
l
对于那些需要从
Spring
容器获取信息,然后产生描述信息的工具来说,装配过程信息不可利用;
l
当容器中只存在唯一一个与
setter
方法或者构造器参数类型匹配的
bean
定义的时候,自装配模式才能正常工作。如果存在任何潜在的不确定性,那么你最好还是进行显式装配。
具体使用哪种方式都没有“对”或者“错”。但在一个项目中,保持一致性还是最好的方式;例如,如果在项目中基本没有使用自装配模式,当你只是在一两个
bean
定义中采用这种方式的时候,很可能让人产生迷惑。
3.3.6
.1
禁止
Bean
的自装配
你也可以禁止
bean
的自装配模式。当使用
Spring
的
XML
配置
bean
时,
<bean/>
的
'autowire-candidate'
属性可以设置成
’false’
;这样,容器就会将该
bean
从自装配体系中排除出去。
当你需要指定某个
bean
禁止以自装配方式注射到其他
bean
的时候,这个属性就变得
很有用。当然,这并不是说,被排除的
bean
自身不能使用自装配方式
...
,而是说它自身不可以作为自装配的候选对象。
3.3.7
依赖检查
Spring
的
IoC
容器可以检查当前容器中的
bean
的未解析依赖的存在性。检查的内容包括那些没有在
bean
定义中设置值的
bean
属性,或者那些应该通过自装配模式设置的属性。
当你需要确认
bean
的所有属性(或某种类型的所有属性)是否被设置时,该特性就显得很有用了。当然,大多数情况下,
bean
会有属性的默认值,或者有些属性在有些场景下不需要赋值,因此,该特性最好有节制的使用。依赖检查同样可以对于单独的
bean
进行配置。依赖检查包括几种不同的模式。使用
XML
配置时,通过指定
'dependency-check'
属性来实现。该属性可以取下面的值。
表
3.3.
依赖检查模式
模式
|
解释
|
none
|
不执行依赖检查。即使
bean
属性没有赋值也没有关系。
|
simple
|
对于原始类型和集合类型属性的依赖检查。(不检查协作者类型依赖,如
bean
的引用)
|
object
|
对于协作者的依赖检查。
|
all
|
对于协作者、原始类型和集合类型的依赖检查。
|
如果是
Java5
(
Tiger
)用户,可以参考源码级的注释,参考第
25.3.1
章,“
@Required
”。
3.3.8
方法注射
在大多数应用场合,容器中的
bean
主要以单例方式存在。如果某个单例
bean
需要和另外一个单例
bean
合作,或者某个非单例
bean
需要和另外一个非单例
bean
合作,通常的处理方式是将一个定义为另外一个
bean
的属性。然后,对于
bean
生命周期不同的时,存在一个问题。考虑下面这种情况:单例
bean A
需要使用非单例
bean B
(原型),可能
A
的每个方法均需要调用
B
。容器仅创建
A
的一个实例,因此只可以设置
A
的属性一次。不可能每次当
B
被请求的时候,都用新的
B
实例来设置。
对于这种情况的一个处理方式是一定程度上放弃控制反转机制。
A
可以实现
BeanFactoryAware
接口,这样可以被容器感知,然后使用程序方式让容器在需要的时候重新请求
B
的实例,如调用
getBean(“B”)
方法。参看下面的例子:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// lots of Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class CommandManager implements BeanFactoryAware {
private BeanFactory beanFactory;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// the Command returned here could be an implementation that executes asynchronously, or whatever
protected Command createCommand() {
return (Command) this.beanFactory.getBean("command"); // notice the Spring API dependency
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
上面的例子不是一个合适的解决方法,因为,业务代码和
Spring
框架出现耦合。方法注射
-Spring IoC
容器的高级特性,可以用清晰的方式处理这种情况。
3.3.8
.1
“查找方式”(
lookup
)的方法注射
查找方式的方法注射基于容器对于它管理的
bean
的方法重载能力实现,这样,容器可以查找需要的
bean
,然后返回。上面提到的场合中,需要查找的
bean
是原型的(当然,也可以查找单例,但对于单例可以直接注射实例)。
Spring
框架使用基于
CGLIB
库的字节码创建方式,动态的创建重载的方法子集,并以此实现方法注射。
看了前面的代码片断(
CommandManager
类),
Spring
容器会动态实现对于
createCommand()
方法的重载。这样,
CommandManager
类不会产生对于
Spring
框架的依赖,可以参看下面改写后的例子:
package fiona.apple;
// no more Spring imports!
public class CommandManager {
public Object process(Object command) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// mmm, but where is the implementation of this method?
protected abstract CommandHelper createHelper();
}
该类(
CommandManager
)包含了要被注射的方法,该方法必须符合下面的形式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,动态创建的子类将会实现它。如果不是,那么将会重载它。参看下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
Bean commandManager
会在每次需要
bean command
的时候调用它自己的
createCommand()
方法。
注意,构造器和
setter
注射都可以使用查找方法注射的方式。
要让动态生成的子类正常运行,那么必须把
CGLIB
这个
jar
放在类路径上(
classpath
)。此外,将要被继承的类不可以是
final
的,并且需要被重载的方法也不可以是
final
的。而且,测试类的抽象方法也有其必要性,因此,你可以自己实现该类的子类,并提供抽象方法的一个实现。最后,要进行方法注射的
bean
不可以被序列化。

感兴趣的读者可能发现
ServiceLocatorFactoryBean
(位于
org.springframework.beans.factory.config
包)可以利用
...
使用方法类似于
ObjectFactoryCreatingFactoryBean
,但允许你使用你自己的
lookup
接口,而不是必须使用
Spring
指定的接口,如
ObjectFactory
。可以参考
java
文档(很多)进一步了解
ServiceLocatorFactoryBean
的使用方法(这种方式有利于进一步减少对于
Spring
框架的依赖)。
3.3.8
.2
任意的方法替换
相比
lookup
方法注射方式而言,还有一种不太用的方法注射方法,可以用方法替代另外其他方法。用户可以忽略本节的剩余部分(描述了
Spring
的高级特性),等到使用的时候再看。
当使用
XML
配置方式时,
replaced-method
元素用来指定方法的另外一个替代方法。参看下面的类,包括了将要被替换的方法
computeValue
:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
另外一个实现了
org.springframework.beans.factory.support.MethodReplacer
接口的类提供了另外一个方法。
/** meant to be used to override the existing computeValue
implementation in MyValueCalculator */
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
下面的
bean
定义将完成方法替代:
<bean id="myValueCalculator class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
可以使用
<replaced-method>
元素中的一个或者多个
<arg-type>
元素来说明将要被替代的方法的参数特点。注意,对于参数的说明只在有方法需要被替代,而且类中存在多个变量的时候才需要。为了方便,参数类型的描述可以采用简写方式。例如,下面的都是匹配
java.lang.String
类型的。
java.lang.String
String
Str
由于参数的数量大多数情况下足以区分每个可能的选择,这种方式可以节省许多的手工输入工作,你只需要手工输入足以表达参数类型的最短的字符串就可以了。