依赖项和配置详情

如前一节所述,你可以将 Bean 属性和构造函数参数定义为对其他托管 Bean(协作对象)的引用,或者定义为内联的值。Spring 基于 XML 的配置元数据在其 <property/><constructor-arg/> 元素中支持子元素类型来实现此目的。

直值(Primitive 类型、String 等)

<property/> 元素的 value 属性将属性或构造函数参数指定为可人工阅读的字符串表示形式。Spring 的转换服务用于将这些值从 String 转换为属性或参数的实际类型。以下示例展示了如何设置各种值:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<!-- results in a setDriverClassName(String) call -->
	<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-namespace进行更简洁的 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 更简洁。然而,错误拼写是在运行时而非设计时发现的,除非你使用支持自动属性完成(property completion)的 IDE(例如 IntelliJ IDEASpring Tools for Eclipse),当你创建 Bean 定义时。强烈推荐此类 IDE 辅助。

你也可以配置一个 java.util.Properties 实例,如下所示:

<bean id="mappings"
	class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

	<!-- typed as a 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="theClientBean" class="...">
	<property name="targetName" ref="theTargetBean"/>
</bean>

第一种形式优于第二种形式,因为使用 idref 标签允许容器在部署时验证引用的命名 Bean 是否实际存在。在第二种变体中,对传递给客户端 Bean 的 targetName 属性的值不执行任何验证。只有当客户端 Bean 实际实例化时,才能发现拼写错误(很可能导致致命结果)。如果客户端 Bean 是一个原型 Bean,这个拼写错误和由此产生的异常可能只有在容器部署很久之后才能发现。

idref 元素上的 local 属性在 4.0 Bean XSD 中不再支持,因为它不再提供高于常规 Bean 引用的价值。升级到 4.0 schema 时,请将现有的 idref local 引用更改为 idref bean

在配置 AOP 拦截器时,ProxyFactoryBean Bean 定义中的 <idref/> 元素是一个带来价值的常用位置(至少在 Spring 2.0 之前的版本中)。在使用 <idref/> 元素指定拦截器名称时,可以防止误拼拦截器 ID。

引用其他 Bean(协作对象)

ref 元素是 <constructor-arg/><property/> 定义元素内的最后一个元素。在此处,你将 Bean 的指定属性的值设置为对容器管理的另一个 Bean(协作对象)的引用。被引用的 Bean 是其属性要设置的 Bean 的一个依赖项,它在属性设置之前根据需要按需初始化。(如果协作对象是单例 Bean,它可能已经被容器初始化)。所有引用最终都是对另一个对象的引用。作用域和验证取决于你通过 beanparent 属性指定其他对象的 ID 或名称。

通过 <ref/> 标签的 bean 属性指定目标 Bean 是最通用的形式,允许引用同一容器或父容器中的任何 Bean,无论它是否在同一个 XML 文件中。bean 属性的值可以与目标 Bean 的 id 属性相同,或者与目标 Bean 的 name 属性中的某个值相同。以下示例展示了如何使用 ref 元素:

<ref bean="someBean"/>

通过 parent 属性指定目标 Bean 会创建对当前容器的父容器中的 Bean 的引用。parent 属性的值可以与目标 Bean 的 id 属性相同,或者与目标 Bean 的 name 属性中的某个值相同。目标 Bean 必须位于当前容器的父容器中。你主要应该在容器具有层级结构,并且你想要用一个与父 Bean 同名的代理来包装父容器中的现有 Bean 时使用这种 Bean 引用变体。以下两段示例展示了如何使用 parent 属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
	<!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context, bean name is the same as the parent bean -->
<bean id="accountService"
	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 here -->
</bean>
ref 元素上的 local 属性在 4.0 Bean XSD 中不再支持,因为它不再提供高于常规 Bean 引用的价值。升级到 4.0 schema 时,请将现有的 ref local 引用更改为 ref bean

内部 Bean

<property/><constructor-arg/> 元素内部的 <bean/> 元素定义了一个内部 Bean,如下例所示:

<bean id="outer" class="...">
	<!-- instead of using a reference to a target bean, simply define the target bean inline -->
	<property name="target">
		<bean class="com.example.Person"> <!-- this is the inner 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 集合类型 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">[email protected]</prop>
			<prop key="support">[email protected]</prop>
			<prop key="development">[email protected]</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="an entry" value="just some string"/>
			<entry key="a ref" value-ref="myDataSource"/>
		</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>

映射的键或值,或者集合的值,也可以是以下任何元素:

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">
			<!-- the merge is specified on the child collection definition -->
			<props merge="true">
				<prop key="sales">[email protected]</prop>
				<prop key="support">[email protected]</prop>
			</props>
		</property>
	</bean>
<beans>

请注意,在子 Bean 定义的 adminEmails 属性的 <props/> 元素上使用了 merge=true 属性。当容器解析并实例化子 Bean 时,生成的实例将拥有一个 adminEmails Properties 集合,其中包含子 adminEmails 集合与父 adminEmails 集合合并的结果。以下列表显示了结果:

Properties 集合的值集继承了父 <props/> 中的所有属性元素,并且子集合中 support 属性的值覆盖了父集合中的值。

这种合并行为类似地适用于 <list/><map/><set/> 集合类型。在 <list/> 元素的特定情况下,保留了与 List 集合类型相关的语义(即值的有序集合概念)。父集合的值位于所有子列表的值之前。在 Map、Set 和 Properties 集合类型的情况下,不存在排序。因此,对于容器内部使用的 Map、Set 和 Properties 实现类型所代表的集合类型,没有排序语义生效。

集合合并的限制

你不能合并不同的集合类型(例如 Map 和 List)。如果尝试这样做,会抛出相应的 Exceptionmerge 属性必须在较低层级的、继承的子定义中指定。在父集合定义中指定 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 类型。

Null 值和空字符串值

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-namespace 的 XML 快捷方式

p-namespace 允许你使用 Bean 元素的属性(而不是嵌套的 <property/> 元素)来描述你的属性值、协作 Bean,或者两者兼有。

Spring 支持带有命名空间的可扩展配置格式,这些格式基于 XML Schema 定义。本章讨论的 Bean 配置格式在 XML Schema 文档中定义。然而,p-namespace 没有在 XSD 文件中定义,仅存在于 Spring 核心中。

以下示例显示了两个 XML 片段(第一个使用标准 XML 格式,第二个使用 p-namespace),它们解析为相同的结果:

<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-namespace 中的一个名为 email 的属性。这告诉 Spring 包含一个属性声明。如前所述,p-namespace 没有 schema 定义,因此你可以将属性的名称设置为属性名。

下一个示例包含另外两个 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-namespace 包含属性值,还使用特殊格式声明属性引用。第一个 Bean 定义使用 <property name="spouse" ref="jane"/> 创建从 Bean john 到 Bean jane 的引用,而第二个 Bean 定义使用属性 p:spouse-ref="jane" 执行完全相同的操作。在这种情况下,spouse 是属性名,而 -ref 部分表示这不是一个直值,而是对另一个 Bean 的引用。

p-namespace 不如标准 XML 格式灵活。例如,声明属性引用的格式与以 Ref 结尾的属性会发生冲突,而标准 XML 格式则不会。我们建议你仔细选择你的方法,并将其告知你的团队成员,以避免同时使用所有这三种方法生成 XML 文档。

使用 c-namespace 的 XML 快捷方式

使用 p-namespace 的 XML 快捷方式类似,c-namespace 于 Spring 3.1 引入,允许使用内联属性来配置构造函数参数,而不是使用嵌套的 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"/>

	<!-- traditional declaration with optional argument names -->
	<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-namespace declaration with argument names -->
	<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 schema 中定义(它存在于 Spring 核心内部),也需要在 XML 文件中声明它。

在构造函数参数名称不可用的极少数情况下(通常是字节码编译时没有使用 -parameters 标志),你可以回退到参数索引,如下所示:

<!-- c-namespace index declaration -->
<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。为了使这能够正常工作,somethingfred 属性以及 fredbob 属性在 bean 构建后不能是 null。否则,会抛出 NullPointerException