详细的依赖关系和配置
如在上一节中提到的,您可以将bean属性和构造函数参数定义为对其他受管bean(协作者)的引用或作为内联定义的值。Spring的基于XML的配置元数据支持<property/>
和<constructor-arg/>
元素内的子元素类型来实现此目的。
直接值(基本类型、字符串等)
<property/>
元素的value
属性指定属性或构造函数参数作为人类可读的字符串表示。Spring使用转换服务将这些值从String
转换为属性或参数的实际类型。以下示例显示了设置各种值的方式:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 导致setDriverClassName(String)调用 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
以下示例使用p-命名空间来实现更简洁的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
上述XML更为简洁。然而,除非您使用支持在创建bean定义时自动完成属性的IDE(如IntelliJ IDEA或Spring Tools for Eclipse),否则拼写错误将在运行时而非设计时被发现。强烈建议使用此类IDE辅助工具。
您还可以配置java.util.Properties
实例,如下所示:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- 类型为java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring容器通过JavaBeans的PropertyEditor
机制将<value/>
元素内的文本转换为java.util.Properties
实例。这是一个很好的快捷方式,也是Spring团队在少数几个地方更倾向于使用嵌套的<value/>
元素而不是value
属性样式的地方之一。
idref
元素
idref
元素只是一种无误传递另一个容器中的bean的id
(一个字符串值 - 而非引用)到<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"/>
</bean>
第一种形式优于第二种,因为使用idref
标签让容器在部署时验证被引用的命名bean是否实际存在。在第二种变体中,对传递给client
bean的targetName
属性的值不执行任何验证。拼写错误只有在实际实例化client
bean时才会被发现(并导致可能致命的结果)。如果client
bean是一个prototype bean,这种拼写错误和随之而来的异常可能在容器部署后很长时间才被发现。
idref 元素上的local 属性在4.0 beans XSD中不再受支持,因为它不再比普通的bean 引用提供价值。在升级到4.0模式时,请将现有的idref local 引用更改为idref bean 。 |
在配置ProxyFactoryBean
bean定义中,至少在Spring 2.0之前,<idref/>
元素带来价值的常见地方是在配置AOP拦截器时。在指定拦截器名称时使用<idref/>
元素可以防止拼写拦截器ID。
对其他bean的引用(协作者)
ref
元素是<constructor-arg/>
或<property/>
定义元素中的最终元素。在这里,您将bean的指定属性的值设置为对容器中管理的另一个bean(协作者)的引用。被引用的bean是要设置属性的bean的依赖项,并且在需要时按需初始化,然后设置属性。(如果协作者是单例bean,则可能已由容器初始化。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是否通过bean
或parent
属性指定了另一个对象的ID或名称。
通过<ref/>
标签的bean
属性指定目标bean是最一般的形式,允许创建对同一容器或父容器中的任何bean的引用,无论它是否在同一XML文件中。 bean
属性的值可以与目标bean的id
属性相同,也可以与目标bean的name
属性中的一个值相同。以下示例显示了如何使用ref
元素:
<ref bean="someBean"/>
通过parent
属性指定目标bean创建对当前容器的父容器中的bean的引用。 parent
属性的值可以与目标bean的id
属性或name
属性中的一个值相同。目标bean必须在当前容器的父容器中。当您有一系列容器并且希望使用与父bean同名的代理包装现有bean时,主要使用此bean引用变体。以下一对列表显示了如何使用parent
属性:
<!-- 在父上下文中 -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- 根据需要插入依赖项 -->
</bean>
<!-- 在子(后代)上下文中 -->
<bean id="accountService" <!-- bean名称与父bean相同 -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- 注意我们如何引用父bean -->
</property>
<!-- 根据需要插入其他配置和依赖项 -->
</bean>
ref 元素上的local 属性在4.0 beans XSD中不再受支持,因为它不再比普通的bean 引用提供价值。在升级到4.0模式时,请将现有的ref local 引用更改为ref bean 。 |
内部Bean
在<property/>
或<constructor-arg/>
元素内部的<bean/>
元素定义了一个内部Bean,如下例所示:
<bean id="outer" class="...">
<!-- 不使用目标Bean的引用,而是内联定义目标Bean -->
<property name="target">
<bean class="com.example.Person"> <!-- 这是内部Bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部Bean定义不需要一个定义的ID或名称。如果指定了,容器不会将此值用作标识符。容器在创建时也会忽略scope
标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。无法独立访问内部Bean,也无法将其注入到除封闭Bean之外的协作Bean中。
作为一个特例,可以从自定义范围接收销毁回调,例如,对于包含在单例Bean中的请求范围内部Bean。内部Bean实例的创建与其包含Bean相关联,但销毁回调使其参与请求范围的生命周期。这不是一个常见的情况。内部Bean通常只是共享其包含Bean的范围。
集合
<list/>
、<set/>
、<map/>
和<props/>
元素分别设置Java Collection
类型的List
、Set
、Map
和Properties
的属性和参数。以下示例展示了如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- 导致调用setAdminEmails(java.util.Properties)方法 -->
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
<prop key="development">[email protected]</prop>
</props>
</property>
<!-- 导致调用setSomeList(java.util.List)方法 -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- 导致调用setSomeMap(java.util.Map)方法 -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- 导致调用setSomeSet(java.util.Set)方法 -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
地图键或值的值,或集合值,也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持合并集合。应用程序开发人员可以定义一个父<list/>
、<map/>
、<set/>
或<props/>
元素,并使子<list/>
、<map/>
、<set/>
或<props/>
元素继承并覆盖父集合中的值。也就是说,子集合的值是父集合和子集合元素合并的结果,子集合元素覆盖父集合中指定的值。
关于合并的这一部分讨论了父子bean机制。不熟悉父子bean定义的读者可能希望在继续之前阅读相关部分。
以下示例演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- 在子集合定义上指定合并 -->
<props merge="true">
<prop key="sales">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>
请注意,在child
bean定义的adminEmails
属性的<props/>
元素上使用了merge=true
属性。当容器解析和实例化child
bean时,生成的实例具有一个adminEmails
Properties
集合,其中包含合并子adminEmails
集合和父adminEmails
集合的结果。以下清单显示了结果:
子Properties
集合的值集继承自父<props/>
的所有属性元素,而子集合对于support
值覆盖了父集合中的值。
这种合并行为同样适用于<list/>
、<map/>
和<set/>
集合类型。在<list/>
元素的特定情况下,保持与List
集合类型相关的语义(即,有序值集合的概念)。父值位于所有子列表值之前。对于Map
、Set
和Properties
集合类型,不存在排序。因此,对于容器内部使用的相关Map
、Set
和Properties
实现类型的集合类型,不会生效排序语义。
集合合并的限制
您不能合并不同类型的集合(例如Map
和List
)。如果尝试这样做,将抛出适当的Exception
。必须在较低的继承子定义上指定merge
属性。在父集合定义上指定merge
属性是多余的,不会导致所需的合并。
强类型集合
由于Java支持泛型类型,您可以使用强类型集合。也就是说,可以声明一个Collection
类型,使其只能包含(例如)String
元素。如果您使用Spring将强类型Collection
注入到bean中,您可以利用Spring的类型转换支持,使得强类型Collection
实例的元素在添加到Collection
之前被转换为适当的类型。以下Java类和bean定义展示了如何实现:
-
Java
-
Kotlin
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
class SomeClass {
lateinit var accounts: Map<String, Float>
}
<beans>
<bean id="something" class="x.y.SomeClass">
<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>
当something
bean的accounts
属性准备好注入时,关于强类型Map<String, Float>
元素类型的泛型信息可通过反射获得。因此,Spring的类型转换基础设施将识别各种值元素为Float
类型,并将字符串值(9.99
、2.75
和3.99
)转换为实际的Float
类型。
空和空字符串值
Spring将属性等的空参数视为空的字符串
。以下基于XML的配置元数据片段将email
属性设置为空字符串
值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的示例等同于以下Java代码:
-
Java
-
Kotlin
exampleBean.setEmail("");
exampleBean.email = ""
<null/>
元素处理null
值。以下清单显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等同于以下Java代码:
-
Java
-
Kotlin
exampleBean.setEmail(null);
exampleBean.email = null
p-命名空间的XML快捷方式
p-命名空间允许您使用bean
元素的属性(而不是嵌套的<property/>
元素)来描述您的属性值,协作bean,或两者兼而有之。
Spring支持可扩展的配置格式,基于XML Schema定义的命名空间。本章讨论的beans
配置格式在XML Schema文档中定义。但是,p-命名空间未在XSD文件中定义,仅存在于Spring的核心中。
以下示例显示了两个XML片段(第一个使用标准XML格式,第二个使用p-命名空间),它们解析为相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</beans>
示例中在bean定义中使用了p-命名空间的属性email
。这告诉Spring包含属性声明。如前所述,p-命名空间没有模式定义,因此您可以将属性名称设置为属性名称。
下一个示例包括另外两个bean定义,两者都引用另一个bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
此示例不仅包括使用p-命名空间的属性值,还使用特殊格式声明属性引用。第一个bean定义使用<property name="spouse" ref="jane"/>
创建从bean john
到bean jane
的引用,而第二个bean定义使用p:spouse-ref="jane"
作为属性来执行完全相同的操作。在这种情况下,spouse
是属性名称,而-ref
部分表示这不是直接值,而是对另一个bean的引用。
p-命名空间不像标准XML格式那样灵活。例如,用于声明属性引用的格式与以Ref 结尾的属性冲突,而标准XML格式则没有。我们建议您谨慎选择您的方法,并与团队成员沟通,以避免生成同时使用所有三种方法的XML文档。 |
c-命名空间的XML快捷方式
类似于p-命名空间的XML快捷方式,在Spring 3.1中引入的c-命名空间允许内联属性来配置构造函数参数,而不是嵌套的constructor-arg
元素。
以下示例使用c:
命名空间来执行与基于构造函数的依赖注入相同的操作:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- 使用可选参数名称的传统声明 -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
</bean>
<!-- 使用c-命名空间声明参数名称 -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
</beans>
c:
命名空间使用与p:
相同的约定(用于bean引用的尾随-ref
)来根据名称设置构造函数参数。同样,即使它在XSD模式中未定义(它存在于Spring核心中),也需要在XML文件中声明它。
在极少数情况下,如果构造函数参数名称不可用(通常是因为字节码是在没有调试信息的情况下编译的),您可以回退到参数索引,如下所示:
<!-- c-命名空间索引声明 -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
由于XML语法,索引表示法需要前导_ 的存在,因为XML属性名称不能以数字开头(尽管一些IDE允许)。<constructor-arg> 元素也有相应的索引表示法,但通常不常用,因为通常情况下声明的顺序就足够了。 |
实际上,构造函数参数的解析机制在匹配参数方面非常高效,因此除非确实需要,我们建议在整个配置中始终使用名称表示法。
复合属性名称
当设置bean属性时,您可以使用复合或嵌套属性名称,只要路径的所有组件(除了最终属性名称)不为null
。考虑以下bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something
bean具有一个fred
属性,该属性具有一个bob
属性,该属性具有一个sammy
属性,最终的sammy
属性被设置为值123
。为了使其工作,something
的fred
属性和fred
的bob
属性在bean构造后不能为null
。否则,将抛出NullPointerException
。