依赖项和配置详解

上一节所述,您可以将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-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://127.0.0.1:3306/mydb"
		p:username="root"
		p:password="misterkaoli"/>

</beans>

前面的XML更加简洁。但是,除非您使用支持在创建bean定义时自动完成属性的IDE(例如IntelliJ IDEASpring Tools for Eclipse),否则类型错误会在运行时而不是设计时被发现。强烈推荐此类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="theClientBean" class="...">
	<property name="targetName" ref="theTargetBean"/>
</bean>

第一种形式优于第二种形式,因为使用idref标签可以让容器在部署时验证引用的命名bean实际上是否存在。在第二种变体中,不会对传递给client bean的targetName属性的值进行任何验证。类型错误仅在实际实例化client bean时才会被发现(很可能导致致命结果)。如果client bean是一个prototype bean,则此类型错误和由此产生的异常可能在容器部署很久之后才会被发现。

在Spring 2.0之前的版本中,<idref/>元素带来价值的一个常见地方是在ProxyFactoryBean bean定义中配置AOP拦截器。当您指定拦截器名称时,使用<idref/>元素可以防止您拼写错误拦截器ID。

<idref/>元素在4.0 beans XSD中已不再支持`local`属性,因为它不再比常规的`bean`引用更有价值。升级到4.0模式时,请将现有的idref local引用更改为idref bean

对其他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引用不再比普通的bean引用提供更多价值,因此在4.0 Beans XSD中不再支持ref元素上的local属性。升级到4.0模式时,请将现有的ref local引用更改为ref bean

内部Bean

<bean/>元素位于<property/><constructor-arg/>元素内定义了一个内部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定义的读者可能希望在继续之前阅读相关章节

以下示例演示了集合合并

<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)。如果尝试这样做,则会抛出相应的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.992.753.99)转换为实际的Float类型。

空值和空字符串值

Spring将属性等的空参数视为空String。以下基于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),通过其名称设置构造函数参数。类似地,即使它没有在XSD模式中定义(它存在于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的值。为了使这能够工作,在构造Bean后,somethingfred属性和fredbob属性必须不能为null。否则,将抛出NullPointerException