Spring揭秘-BeanFactory的XML之旅之(上)
BeanFactory的XML之旅
XML格式的容器信息管理方式是Spring提供的最为强大、支持最为全面的方式。从Spring的参考文档到各Spring相关书籍,都是按照XML的配置进行说明的,这部分内容可以让你充分领略到Spring的IoC容器的魅力,以致于我们也不得不带你初次或者再次踏上Spring的XML之旅。
<beans>
和<bean>
所有使用XML文件进行配置信息加载的Spring IoC容器,包括BeanFactory和ApplicationContext的所有XML相应实现,都使用统一的XML格式。在Spring 2.0版本之前,这种格式由Spring提供的DTD规定,也就是说,所有的Spring容器加载的XML配置文件的头部,都需要以下形式的DOCTYPE声明:
1 |
|
从Spring 2.0版本之后, Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的DOCTYPE进行配置文件格式的限定,又引入了基于XML Schema的文档声明。所以, Spring 2.0之后,同样可以使用如下所展示的基于XSD的文档声明。
1 |
|
不过,不管使用哪一种形式的文档声明,实际上限定的元素基本上是相同的。让我们从最顶层的元素开始,看一下这两种文档声明都限定了哪些元素吧!
所有注册到容器的业务对象,在Spring中称之为Bean。所以,每一个对象在XML中的映射也自然而然地对应一个叫做<bean>
的元素。既然容器最终可以管理所有的业务对象,那么在XML中把这些叫做
<beans>
之唯我独尊
<beans>
是XML配置文件中最顶层的元素,它下面可以包含0或者1个<description>
和多个<bean>
以及<import>
或者<alias>
,如图所示。<beans>
作为所有<bean>
的“统帅”,它拥有相应的属性(attribute)对所辖的<bean>
进行统一的默认行为设置,包括如下几个。
- default-lazy-init。 其值可以指定为true或者false,默认值为false。用来标志是否对所有的
<bean>
进行延迟初始化。 - default-autowire。 可以取值为no、 byName、 byType、 constructor以及autodetect。默认值为no,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。
- default-dependency-check。 可以取值none、 objects、 simple以及all,默认值为none,即不做依赖检查。
- default-init-method。 如果所管辖的
<bean>
按照某种规则,都有同样名称的初始化方法的话,可以在这里统一指定这个初始化方法名,而不用在每一个<bean>
上都重复单独指定。 - default-destroy-method。 与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。
注:当然,如果你不清楚上面这些默认的属性具体有什么用,那也不必着急。在看完对<bean>
的讲解之后,再回头来看,就会明了多了。给出这些信息,是想让你知道,如果在某个场景下需要对大部分<bean>
都重复设置某些行为的话,可以回头看一下,利用<beans>
是否可以减少这种不必要的工作。
<description>
、 <import>
和<alias>
之所以把这几个元素放到一起讲解,是因为通常情况下它们不是必需的。不过,了解一下也没什么不好,不是吗?
<description>
可以通过在配置的文件中指定一些描述性的信息。通常情况下,该元素是省略的。当然,如果愿意, <description>
随时可以为我们效劳。<import>
通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件中通过<import>
元素对其所依赖的配置文件进行引用。比如,如果A.xml中的<bean>
定义可能依赖B.xml中的某些<bean>
定义,那么就可以在A.xml中使用<import>
将B.xml引入到A.xml,以类似于<import resource="B.xml"/>
的形式。
但是, 这个功能在我看来价值不大,因为容器实际上可以同时加载多个配置,没有必要非通过一个配置文件来加载所有配置。不过,或许在有些场景中使用这种方式比较方便也说不定。<alias>
可以通过<alias>
为某些<bean>
起一些“外号”(别名),通常情况下是为了减少输入。比如,假设有个<bean>
,它的名称为dataSourceForMasterDatabase,你可以为其添加一个<alias>
,像这样<alias name="dataSourceForMasterDatabase" alias="masterDataSource"/>
。以后通过dataSourceForMasterDatabase或者masterDataSource来引用这个<bean>
都可以,只要你觉得方便就行。
bean
每个业务对象作为个体,在Spring的XML配置文件中是与<bean>
元素一一对应的。窥一斑而知全豹,只要我们了解单个的业务对象是如何配置的,剩下的就可以“依葫芦画瓢”了。所以,我们先从最简单的单一对象配置开始吧!如下代码演示了最基础的对象配置形式:
1 |
|
- id属性
通常,每个注册到容器的对象都需要一个唯一标志来将其与“同处一室”的“兄弟们”区分开来,就好像我们每一个人都有一个身份证号一样(重号的话就比较麻烦)。通过id属性来指定当前注册对象的beanName是什么。这里,通过id指定beanName为djNewsListener。实际上,并非任何情况下都需要指定每个<bean>
的id,有些情况下, id可以省略,比如后面会提到的内部<bean>
以及不需要根据beanName明确依赖关系的场合等。
除了可以使用id来指定<bean>
在容器中的标志,还可以使用name属性来指定<bean>
的别名(alias)。比如,以上定义,我们还可以像如下代码这样,为其添加别名:与id属性相比,name属性的灵活之处在于,name可以使用id不能使用的一些字符,比如/。而且还可以通过逗号、空格或者冒号分割指定多个name。 name的作用跟使用1
2
3
4<bean id="djNewsListener"
name="/news/djNewsListener,dowJonesNewsListener"
class="..impl.DowJonesNewsListener">
</bean>为id指定多个别名基本相同: 1
2<alias name="djNewsListener" alias="/news/djNewsListener"/>
<alias name="djNewsListener" alias="dowJonesNewsListener"/> - class属性
每个注册到容器的对象都需要通过<bean>
元素的class属性指定其类型,否则,容器可不知道这个对象到底是何方神圣。在大部分情况下,该属性是必须的。仅在少数情况下不需要指定,如后面将提到的在使用抽象配置模板的情况下。
Help Me, Help You
在大部分情况下,我们都不太可能选择单独“作战”,业务对象也是;各个业务对象之间会相互协作来更好地完成同一使命。这时,各个业务对象之间的相互依赖就是无法避免的。对象之间需要相互协作,在横向上它们存在一定的依赖性。而现在我们就是要看一下,在Spring的IoC容器的XML配置中,应该如何表达这种依赖性。
既然业务对象现在都符合IoC的规则,那么要了解的表达方式其实也很简单,无非就是看一下构造方法注入和setter方法注入通过XML是如何表达的而已。那么,让我们开始吧!
构造方法注入的XML之道
按照Spring的IoC容器配置格式,要通过构造方法注入方式,为当前业务对象注入其所依赖的对象,需要使用<constructor-arg>
。正常情况下,如以下代码所示:
1 |
|
对于<ref>
元素,稍后会进行详细说明。这里你只需要知道,通过这个元素来指明容器将为djNewsProvider这个<bean>
注入通过<ref>
所引用的Bean实例。这种方式可能看起来或者编写起来不是很简洁,最新版本的Spring也支持配置简写形式,如以下代码所示:
1 |
|
简洁多了不是嘛?其实,无非就是表达方式不同而已,实际达到的效果是一样的。有些时候,容器在加载XML配置的时候,因为某些原因,无法明确配置项与对象的构造方法参数列表的一一对应关系,就需要请<constructor-arg>
的type或者index属性出马。比如,对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象。
- type属性
假设有一个对象定义如下所示:该类声明了两个构造方法,分别都只是传入一个参数,且参数类型不同。这时,我们可以进行配置,如以下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class MockBusinessObject {
private String dependency1;
private int dependency2;
public MockBusinessObject(String dependency)
{
this.dependency1 = dependency;
}
public MockBusinessObject(int dependency)
{
this.dependency2 = dependency;
}
...
@Override
public String toString() {
return new ToStringBuilder(this) .append("dependency1", dependency1)
.append("dependency2", dependency2).toString();
}
}如果从BeanFactory取得该对象并调用toString()查看的话,我们会发现Spring调用的是第一个构造方法,因为输出是如下内容:1
2
3
4
5<bean id="mockBO" class="..MockBusinessObject">
<constructor-arg>
<value>111111</value>
</constructor-arg>
</bean>但是,如果我们想调用的却是第二个传入int类型参数的构造方法,又该如何呢?可以使用type属性,通过指定构造方法的参数类型来解决这一问题,配置内容如下代码所示:1
..MockBusinessObject@f73c1[dependency1=111111,dependency2=0]
现在,我们得到了自己想要的对象实例,如下的控制台输出信息印证了这一点:1
2
3
4
5<bean id="mockBO" class="..MockBusinessObject">
<constructor-arg type="int">
<value>111111</value>
</constructor-arg>
</bean>1
..MockBusinessObject@f73c1[dependency1=<null>,dependency2=111111]
- index属性
当某个业务对象的构造方法同时传入了多个类型相同的参数时, Spring又该如何将这些配置中的信息与实际对象的参数一一对应呢?好在,如果配置项信息和对象参数可以按照顺序初步对应的话,Spring还是可以正常工作的,如下所示。并且,配置内容如以下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MockBusinessObject {
private String dependency1;
private String dependency2;
public MockBusinessObject(String dependency1,String dependency2)
{
this.dependency1 = dependency1;
this.dependency2 = dependency2;
}
...
@Override
public String toString() {
return new ToStringBuilder(this)
.append("dependency1", dependency1)
.append("dependency2", dependency2).toString();
}
}那么,我们可以得到如下对象:1
2
3
4<bean id="mockBO" class="..MockBusinessObject">
<constructor-arg value="11111"/>
<constructor-arg value="22222"/>
</bean>但是,如果要让“ 11111”作为对象的第二个参数,而将“ 22222”作为第一个参数来构造对象,又该如何呢?好!可以颠倒配置项,如以下代码所示:1
..MockBusinessObject@1ef8cf3[dependency1=11111,dependency2=22222]
不过,还有一种方式,那就是像如下代码所示的那样,使用index属性:1
2
3
4<bean id="mockBO" class="..MockBusinessObject">
<constructor-arg value="22222"/>
<constructor-arg value="11111"/>
</bean>这时,同样可以得到想要的对象实例,以下控制台输出表明了这一点:1
2
3
4<bean id="mockBO" class="..MockBusinessObject">
<constructor-arg index="1" value="11111"/>
<constructor-arg index="0" value="22222"/>
</bean>1
..MockBusinessObject@ecd7e[dependency1=22222,dependency2=11111]
setter方法注入的XML之道
与构造方法注入可以使用<constructor-arg>
注入配置相对应, Spring为setter方法注入提供了<property>
元素。
<property>
有一个name属性,用来指定该<property>
将会注入的对象所对应的实例变量名称。之后通过value或者ref属性或者内嵌的其他元素来指定具体的依赖对象引用或者值,如以下代码所示:
1 |
|
当然,如果只是使用
以上配置形式还可以简化为如下形式:
1 |
|
使用<property>
的setter方法注入和使用<constructor-arg>
的构造方法注入并不是水火不容的。实际上,如果需要,可以同时使用这两个元素:
1 |
|
当然,现在需要MockBusinessObject提供一个只有一个String类型参数的构造方法,并且为dependency2提供了相应的setter方法。如下代码演示了符合条件的一个业务对象定义。
1 |
|
<property>
和<constructor-arg>
中可用的配置项
之前我们看到,可以通过在<property>
和<constructor-arg>
这两个元素内部嵌套<value>
或者<ref>
,来指定将为当前对象注入的简单数据类型或者某个对象的引用。不过,为了能够指定多种注入类型, Spring还提供了其他的元素供我们使用,这包括bean、 ref、 idref、 value、 null、 list、 set、 map、props。下面逐个详细讲述。
注:以下涉及的所有内嵌元素,对于
<value>
可以通过value为主体对象注入简单的数据类型,不但可以指定String类型的数据,而且可以指定其他Java语言中的原始类型以及它们的包装器( wrapper)类型,比如int、 Integer等。容器在注入的时候,会做适当的转换工作(我们会在后面揭示转换的奥秘)。之前已经见过如何使用<value>
了,不过让我们通过如下代码来重新认识一下它:当然,也可以使用如下的简化形式(不过这里的value是以上一层元素的属性身份出现):1
2
3
4
5
6<constructor-arg>
<value>111111</value>
</constructor-arg>
<property name="attributeName">
<value>222222</value>
</property>需要说明的是,1
2<constructor-arg value="111111"/>
<property name="attributeName" value="222222"/><value>
是最“底层”的元素,它内部不能再嵌套使用其他元素了。<ref>
使用ref来引用容器中其他的对象实例,可以通过ref的local、 parent和bean属性来指定引用的对象的beanName是什么。如下演示了ref及其三个对应属性的使用情况。或者1
2
3<constructor-arg>
<ref local="djNewsPersister"/>
</constructor-arg>或者1
2
3<constructor-arg>
<ref parent="djNewsPersister"/>
</constructor-arg>local、 parent和bean的区别在于:1
2
3<constructor-arg>
<ref bean="djNewsPersister"/>
</constructor-arg>local只能指定与当前配置的对象在同一个配置文件的对象定义的名称(可以获得XML解析器的id约束验证支持);
parent则只能指定位于当前容器的父容器中定义的对象引用;
bean则基本上通吃,所以,通常情况下,直接使用bean来指定对象引用就可以了。<ref>
的定义为<!ELEMENT ref EMPTY>
,也就是说,它下面没有其他子元素可用了,别硬往人家肚子里塞东西哦。<idref>
如果要为当前对象注入所依赖的对象的名称,而不是引用,那么通常情况下,可以使用<value>
来达到这个目的,使用如下形式:但这种场合下,使用idref才是最为合适的。因为使用idref,容器在解析配置的时候就可以帮你检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在。1
2
3<property name="newsListenerBeanName">
<value>djNewsListener</value>
</property>
毕竟,输错名字的问题很常见。以下代码演示了idref的使用:这段配置跟上面使用1
2
3<property name="newsListenerBeanName">
<idref bean="djNewsListener"/>
</property><value>
达到了相同的目的,不过更加保险。如果愿意,也可以通过local而不是bean来指定最终值,不过bean比较大众化哦。- 内部
<bean>
使用<ref>
可以引用容器中独立定义的对象定义。但有时,可能我们所依赖的对象只有当前一个对象引用,或者某个对象定义我们不想其他对象通过<ref>
引用到它。这时,我们可以使用内嵌的<bean>
,将这个私有的对象定义仅局限在当前对象。 对于FX新闻系统的DowJonesNewsListener而言,实际上只有道琼斯的FXNewsProvider会使用它。而且,我们也不想让其他对象引用到它。
为此完全可以将它配置为内部<bean>
的形式。这样,该对象实例就只有当前的djNewsProvider可以使用,其他对象无法取得该对象的引用。1
2
3
4
5
6
7
8
9<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg index="0">
<bean class="..impl.DowJonesNewsListener">
</bean>
</constructor-arg>
<constructor-arg index="1">
<ref bean="djNewsPersister"/>
</constructor-arg>
</bean>
注意: 因为就只有当前对象引用内部所指定的对象,所以,内部 的id不是必须的。当然,如果你愿意指定id,那也是无所谓的。如下所示: 1
2
3
4<constructor-arg index="0">
<bean id="djNewsListener" class="..impl.DowJonesNewsListener">
</bean>
</constructor-arg>