依赖项和配置详解

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

直接值(基本类型、字符串等)

<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://127.0.0.1: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://127.0.0.1:3306/mydb"
		p:username="root"
		p:password="misterkaoli"/>

</beans>

前面的 XML 更简洁。但是,除非您使用支持自动属性完成的 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://127.0.0.1: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 是一个 原型 bean,则此打字错误以及由此产生的异常可能只会在容器部署很久之后才会被发现。

idref 元素上的 local 属性在 4.0 beans XSD 中不再受支持,因为它不再提供比常规 bean 引用更有价值的功能。在升级到 4.0 模式时,将现有的 idref local 引用更改为 idref bean

<idref/> 元素带来价值的一个常见地方(至少在早于 Spring 2.0 的版本中)是在 ProxyFactoryBean bean 定义中配置 AOP 拦截器。在指定拦截器名称时使用 <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 id="accountService" <!-- bean name is the same as 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 here -->
</bean>
ref 元素上的 local 属性在 4.0 beans XSD 中不再受支持,因为它不再提供比常规 bean 引用更有价值的功能。升级到 4.0 模式时,将现有的 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 Collection 类型 ListSetMapProperties 的属性和参数。以下示例展示了如何使用它们

<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 和子 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>

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

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

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

集合合并的限制

您不能合并不同的集合类型(例如,MapList)。如果您尝试这样做,将抛出适当的 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.992.753.99)转换为实际的 Float 类型。

空值和空字符串值

Spring 将属性等的空参数视为空 Strings。以下基于 XML 的配置元数据片段将 email 属性设置为空 String 值("")。

<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 模式定义。本章讨论的 beans 配置格式在 XML 模式文档中定义。但是,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 定义中名为 email 的 p 命名空间中的属性。这告诉 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"/>

	<!-- 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),通过名称设置构造函数参数。类似地,它需要在XML文件中声明,即使它没有在XSD模式中定义(它存在于Spring核心内部)。

对于构造函数参数名称不可用(通常如果字节码是在没有调试信息的情况下编译的)的罕见情况,您可以使用回退到参数索引,如下所示

<!-- 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