【Spring系列】- 如何表达Bean之间依赖性
一、<beans>
和 <bean>
XML格式的容器信息管理方式是Spring提供的最为强大、支持最为全面的方式。所有使用XML文件进行配置信息加载的Spring IoC容器 (包括BeanFactory和ApplicationContext的XML相应实现) 都使用统一的XML格式
- 在Spring 2.0版本之前,XML格式由Spring提供的DTD规定,即所有Spring容器加载的XML配置文件的头部都需要DOCTYPE声明
- 从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的DOCTYPE进行配置文件格式的限定,又引入了基于XML Schema的文档声明(基于XSD的文档声明)
1. XML总体格式
每一个对象在XML中的映射也对应一个叫做的元素。在XML中把这些叫做的元素组织起来的,就叫做,多个组成一个,是XML配置文件中最顶层的元素,它下面可以包含0或者1个和多个以及或者
2. <beans>
的属性attribute
用于对所辖的进行统一的默认行为设置,包括:
-
default-lazy-init:true或false,默认值为false。用来标志是否对所有的进行延迟初始化
-
default-autowire:可以取值no、byName、byType、constructor或autodetect。默认值为no,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式
-
default-dependency-check:可以取值none、objects、simple以及all。默认值为none,即不做依赖检查
-
default-init-method:如果所管辖的按照某种规则,都有同样名称的初始化方法的话,可以在这里统一指定这个初始化方法名,而不用在每一个上都重复单独指定
-
default-destroy-method:与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定
3. <description>
、<import>
和 <alias>
- 可以通过在配置的文件中指定一些描述性的信息。通常省略
- 通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。可以在主要的配置文件中通过元素对其所依赖的配置文件进行引用,类似于。但是,这个功能在我看来价值不大,因为容器实际上可以同时加载多个配置,没有必要非通过一个配置文件来加载所有配置
- 可以通过为某些起别名,通常情况下是为了减少输入。比如,假设有个,它的名称为dataSourceForMasterDatabase,可以为其添加一个,像这样
4. Bean的配置形式
-
每个业务对象作为个体,在Spring的XML配置文件中是与元素一 一对应的
-
最基础的对象配置形式:
<bean id="djNewsListener" name="/news/djNewsListener,dowJonesNewsListener" class="..impl.DowJonesNewsListener"> </bean>
-
id属性
用于标识区分每个注册到容器的对象。通过id属性来指定当前注册对象的beanName是什么,这里,通过id指定beanName为djNewsListener。有些情况下,id可以省略,除了可以使用id来指定在容器中的标志,还可以使用name属性来指定的别名(alias)。与id属性相比,name属性的灵活之处在于,name可以使用id不能使用的一些字符,如/,还可以通过逗号、空格或冒号分割指定多个name。name的作用跟使用为id指定多个别名基本相同
-
class属性
每个注册到容器的对象都需要通过元素的class属性指定其类型。在大部分情况下,该属性是必须的,仅在少数情况下不需要指定,如在使用抽象配置模板的情况下
二、如何表达依赖性
各个业务对象之间会相互协作来更好地完成同一使命,这时,各个业务对象之间的相互依赖就是无法避免的。
在Spring的IoC容器的XML配置中,如何表达依赖性:
1. XML中通过构造方法注入依赖
-
按照Spring的IoC容器配置格式,要通过构造方法注入方式,为当前业务对象注入其所依赖的对象,需要使用,并通过元素来指明容器将为djNewsProvider这个注入通过所引用的Bean实例
<bean id="djNewsProvider" class="..FXNewsProvider"> <constructor-arg> <ref bean="djNewsListener"/> </constructor-arg> <constructor-arg> <ref bean="djNewsPersister"/> </constructor-arg> </bean>
最新版本的Spring也支持配置简写形式:
<bean id="djNewsProvider" class="..FXNewsProvider"> <constructor-arg ref="djNewsListener"/> <constructor-arg ref="djNewsPersister"/> </bean>
-
若容器在加载XML配置时,无法明确配置项与对象的构造方法参数列表的一一对应关系,就需要用到的type或index属性。比如,对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象
-
type属性: 指定采用哪个参数类型的构造方法
<bean id="mockBO" class="..MockBusinessObject"> <constructor-arg type="int"> <value>111111</value> </constructor-arg> </bean>
-
index属性: 当某个业务对象的构造方法同时传入了多个类型相同的参数时,如果配置项信息和对象参数可以按照顺序初步对应的话,Spring还是可以正常工作的
<bean id="mockBO" class="..MockBusinessObject"> <constructor-arg value="11111"/> <constructor-arg value="22222"/> </bean>
但如果XML中参数没按顺序给出的话,就得加上index指定是第几个参数了
<bean id="mockBO" class="..MockBusinessObject"> <constructor-arg index="1" value="11111"/> <constructor-arg index="0" value="22222"/> </bean>
-
2. XML中通过 setter方法注入依赖
-
与构造方法注入可以使用注入配置相对应,Spring为setter方法注入提供了元素。有一个name属性(attribute),用来指定该注入的对象的实例变量名称,再通过value或ref属性或内嵌其他元素,来指定具体的依赖对象的引用或值
<bean id="djNewsProvider" class="..FXNewsProvider"> <property name="newsListener"> <ref bean="djNewsListener"/> </property> <property name="newPersistener"> <ref bean="djNewsPersister"/> </property> </bean> // 可以简化为: <bean id="djNewsProvider" class="..FXNewsProvider"> <property name="newsListener" ref="djNewsListener"/> <property name="newPersistener" ref="djNewsPersister"/> </bean>
使用的setter方法注入和使用的构造方法注入并不是水火不容的。实际上,如果需要,可以同时使用这两个元素。比如,业务对象依赖两个对象,但只提供了一个只有一个dependency1参数的构造方法,又为dependency2提供了相应的setter方法,则可以:
<bean id="mockBO" class="..MockBusinessObject"> <constructor-arg value="11111"/> <property name="dependency2" value="22222"/> </bean>
3. <property>
和 <constructor-arg>
中可用的配置项
-
可以通过在和这两个元素内部嵌套或者,来指定将为当前对象注入的简单数据类型或者某个对象的引用。不过,为了能够指定多种注入类型,Spring还提供了其他的元素供使用,包括bean、ref、idref、value、null、list、set、map、props
-
<value>
: 可以通过value为主体对象注入简单的数据类型,不但可以指定String类型的数据,而且可以指定其他Java语言中的原始类型以及它们的包装器(wrapper)类型,比如int、Integer等<constructor-arg> <value>111111</value> </constructor-arg> <property name="attributeName"> <value>222222</value> </property>
-
<ref>
: 使用ref来引用容器中其他的对象实例,可以通过ref的local、parent和bean属性来指定引用的对象的beanName是什么。local、parent和bean的区别在于:local只能指定与当前配置的对象在同一个配置文件的对象定义的名称;parent则只能指定位于当前容器的父容器中定义的对象引用;bean则基本上通吃,所以,通常直接使用bean来指定对象引用就行
<constructor-arg> <ref local="djNewsPersister"/> </constructor-arg> # 或者 <constructor-arg> <ref parent="djNewsPersister"/> </constructor-arg> # 或者 <constructor-arg> <ref bean="djNewsPersister"/> </constructor-arg>
-
<idref>
: 如果要为当前对象注入所依赖的对象的名称,而不是引用,可以使用,但使用idref才是最为合适的。因为使用idref,容器在解析配置时就可以检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在<property name="newsListenerBeanName"> <idref bean="djNewsListener"/> </property>
-
内部
<bean>
: 有时,可能我们所依赖的对象只有一个对象引用,或某个对象定义我们不想其他对象通过引用到它。这时,可以使用内嵌的,将这个私有的对象定义仅局限在当前对象<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>
-
<list>
: 对应注入对象类型为java.util.List及其子类或数组类型的依赖对象,通过可以有序地为当前对象注入以collection形式声明的依赖public class MockDemoObject { private List param1; private String[] param2; // 相应的setter和getter方法 ... }
元素内部可以嵌套其他元素,并且可以像param1所展示的那样夹杂配置:
<property name="param1"> <list> <value> something</value> <ref bean="someBeanName"/> <bean class="..."/> </list> </property> <property name="param2"> <list> <value>stringValue1</value> <value>stringValue2</value> </list> </property>
-
<set>
: 如果说可以帮你有序地注入一系列依赖的话,那么就是无序的。对应注入Java Collection中类型为java.util. Set或者其子类的依赖对象public class MockDemoObject { private Set valueSet; // 相应的setter和getter方法 ... }
从配置上来说,这样多层嵌套、多元素混杂配置是完全没有问题的,但很危险:
<property name="valueSet"> <set> <value> something</value> <ref bean="someBeanName"/> <bean class="..."/> <list> ... </list> </set> </property>
-
<map>
: 与列表(list)使用数字下标来标识元素不同,映射(map)可以通过指定的键(key)来获取相应的值。<set>
: 如果说可以帮你有序地注入一系列依赖的话,那么就是无序的。对应注入Java Collection中类型为java.util. Set或者其子类的依赖对象public class MockDemoObject { private Map mapping; // 相应的setter和getter方法 ... }
对于
<property name="mapping"> <map> <entry key="strValueKey"> <value>something</value> </entry> <entry key="strValueKey2" value="nothing"/> <entry> <key>objectKey</key> <ref bean="someObject"/> </entry> <entry key-ref="lstKey"> <list> ... </list> </entry> </map> </property>
-
<props>
: 是简化后了的public class MockDemoObject { private Properties emailAddrs; // 相应的setter和getter方法 ... }
每个可以嵌套多个,内部只能指定字符串
<property name="valueSet"> <props> <prop key="author">fujohnwang@gmail.com</prop> <prop key="support">support@spring21.cn</prop> </props> </property>
-
<null/>
: 它只是一个空元素,通常用到的场景不是很多。对于String类型来说,如果value注入方式为,那么,得到的结果是"",而不是null。所以,如果需要为某个对象的值注入null,可使用public class MockDemoObject { private Object param2; // 相应的setter和getter方法 ... } // 这么配置实际上就相当于=null,其实这个配置意义不大 // private String param1 = null;
配置:
<property name="param1"> <null/> </property>
4. depends-on
-
通常情况下,可以直接通过之前提到的所有元素,来显式地指定bean之间的依赖关系。这样,容器在初始化当前bean定义的时候,会根据这些元素所标记的依赖关系,首先实例化当前bean定义所依赖的其他bean定义。但是,如果某些时候,我们没有通过类似的元素明确指定对象A依赖于对象B的话,如何让容器在实例化对象A之前首先实例化对象B呢?
-
系统中所有需要日志记录的类,都需要在这些类使用之前首先初始化log4j。那么,就会非显式地依赖于SystemConfigurationSetup的静态初始化块。也就必须在bean定义中使用depends-on来要求容器在初始化自身实例之前首先实例化SystemConfigurationSetup,以保证日志系统的可用
<bean id="classAInstance" class="ClassA" depends-on="configSetup"/> <bean id="configSetup" class="SystemConfigurationSetup"/>
如果说ClassA拥有多个类似的非显式依赖关系,那么,可以在ClassA的depends-on中通过逗号分割各个beanName:
<bean id="classAInstance" class="ClassA" depends-on="configSetup1, configSetup2,..."/> <bean id="configSetup1" class="SystemConfigurationSetup"/> <bean id="configSetup2" class="SystemConfigurationSetup2"/>
5. autowire
-
除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了bean的自动绑定功能。通过的autowire属性,可以指定当前bean采用某种类型的自动绑定模式
-
手工绑定的关系总会覆盖掉自动绑定的
自动绑定对原生类型、String类型和Classes类型以及这些类型的数组是无效的
-
Spring提供了5种自动绑定模式:no、byName、byType、constructor和autodetect
-
no
容器默认的自动绑定模式,也就是不采用任何形式的自动绑定,完全依赖手工配置各个bean之间的依赖关系,以下代码演示的两种配置是等效的:
<bean id="beanName" class="..."/> <bean id="beanName" class="..." autowire="no"/>
-
byName
按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的beanName的值(由id属性指定)进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。对于如下类定义:
public class Foo { private Bar emphasisAttribute; // 相应的setter方法定义 ... } public class Bar {...}
应该使用如下自动绑定定义,才能达到预期的目的:
<bean id="fooBean" class="Foo" autowire="byName"> </bean> <bean id="emphasisAttribute" class="Bar"> </bean>
-
byType
如果bean定义中的autowire=byType,那么容器会根据当前bean定义类型,分析其相应的依赖对象类型,然后到容器所管理的所有bean定义中寻找与依赖对象类型相同的bean定义,然后将找到的符合条件的bean自动绑定到当前bean定义。但如果找到多个,容器解决不了“该选用哪一个”的问题
-
constructor
byName和byType类型的自动绑定模式是针对property的自动绑定,而constructor类型则是针对构造方法参数的类型而进行的自动绑定。它同样是byType类型的绑定模式,如果找到不止一个符合条件的bean定义,容器会报错
public class Foo { private Bar bar; public Foo(Bar arg) { this.bar = arg; } }
使用上也与byType没有太大差别,只不过constructor是应用到需要使用构造方法注入的bean定义之上,匹配的是构造方法的参数类型,而不是实例属性的类型
<bean id="fooBean" class="Foo" autowire="constructor"/> <bean id="emphasisAttribute" class="Bar"> </bean>
-
autodetect
这种模式是byType和constructor模式的结合体:如果对象拥有默认无参数的构造方法,容器会优先考虑byType的自动绑定模式,否则会使用constructor模式;如果通过构造方法注入绑定后还有其他属性没有绑定,容器会使用byType对剩余的对象属性进行自动绑定
-
-
作为所有的统帅,有一个default-autowire属性,它可以帮我们省去为多个单独设置autowire属性的麻烦,default-autowire的默认值为no,即不进行自动绑定
<beans default-autowire="byType"> <bean id="..." class="..."/> ... </beans>
6. dependency-check
可以使用每个的dependency-check属性,检查每个对象某种类型的所有依赖是否全部已经注入完成。可以通过dependency-check指定容器检查特定类型的依赖:
- none:不做依赖检查,容器以此为默认值
- simple:对简单属性类型以及相关的collection进行依赖检查,对象引用类型的依赖除外
- object:只对对象引用类型依赖进行检查
- all:将simple和object相结合,即对简单属性类型以及相应的collection和所有对象引用类型的依赖进行检查
总地来说,控制得力的话,这个依赖检查的功能我们基本可以不考虑使用
7. 延迟初始化 lazy-init
-
与BeanFactory不同,ApplicationContext在容器启动的时候,就会马上对所有的“singleton的bean定义”进行实例化操作。可以通过的lazy-init属性来改变某个或者某些bean定义在ApplicationContext容器中的默认实例化时机:
# 实现ApplicationContext容器在启动时,只实例化not-lazy-init-bean而不实例化lazy-init-bean <bean id="lazy-init-bean" class="..." lazy-init="true"/> <bean id="not-lazy-init-bean" class="..."/>
-
当然,仅指定lazy-init-bean的lazy-init为true,并不意味着容器就一定会延迟初始化该bean的实例。如果某个非延迟初始化的bean定义依赖于lazy-init-bean,那么容器还是会首先实例化lazy-init-bean,然后再实例化后者,导致延迟初始化失败
-
不过可以通过在顶层由统一控制实现延迟初始化
<beans default-lazy-init="true">
三、bean之间的继承关系
除了单独存在的bean以及多个bean之间的横向依赖关系,我们也不能忽略“纵向上”各个bean之间的关系
<bean id="superNewsProvider" class="FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>
<bean id="subNewsProvider" parent="superNewsProvider" class="SpecificFXNewsProvider">
<property name="newsListener">
<ref bean="specificNewsListener"/>
</property>
</bean>
在声明subNewsProvider的时候,可以使用了parent属性,继承superNewsProvider的所有依赖,只需要将特定的属性进行更改,而不要全部又重新定义一遍
parent属性还可以与abstract属性结合使用,达到将相应bean定义模板化的目的
<bean id="newsProviderTemplate" abstract="true">
<property name="newPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>
<bean id="superNewsProvider" parent="newsProviderTemplate" class="FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
</bean>
<bean id="subNewsProvider" parent="newsProviderTemplate" class="SpecificFXNewsProvider">
<property name="newsListener">
<ref bean="specificNewsListener"/>
</property>
</bean>
newsProviderTemplate的bean定义通过abstract属性声明为true,说明这个bean定义不需要实例化,是一个配置模板,不对应任何对象。(加了abstract的Bean,可以不指定class属性)
容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果你不想容器在初始化时实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化