Skip to content

依赖与配置详解 (Dependencies and Configuration in Detail)

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

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

<property/> 元素的 value 属性将属性或构造函数参数指定为可读的字符串表示形式。Spring 的转换服务 (Conversion Service) 用于将这些值从 String 转换为属性或参数的实际类型。

以下示例展示了设置各种值的情况:

xml
<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 配置:

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 更为简洁。然而,除非你使用支持自动属性完成的 IDE(如 IntelliJ IDEASpring Tools),否则拼写错误是在运行时而不是设计时发现的。强烈建议使用此类 IDE 辅助。

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

xml
<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/> 元素的首选防错方式。

xml
<bean id="collaborator" class="..." />

<bean id="client" class="...">
	<property name="targetName">
		<idref bean="collaborator" />
	</property>
</bean>

上述 Bean 定义片段在运行时与以下片段完全等效:

xml
<bean id="collaborator" class="..." />

<bean id="client" class="...">
	<property name="targetName" value="collaborator" />
</bean>

第一种形式优于第二种形式,因为使用 idref 标签可以让容器在部署时验证所引用的命名 Bean 实际上是否存在。在第二种变体中,不对传递给 client Bean 的 targetName 属性的值执行验证。只有当 client Bean 实际实例化时,才会发现拼写错误(很可能导致致命结果)。

AOP 中的价值

在早期的 Spring 版本中,<idref/> 元素的一个常见价值点是在 ProxyFactoryBean 定义中配置 AOP 拦截器。使用 <idref/> 元素指定拦截器名称可以防止你拼错拦截器 ID。

引用其他 Bean (协作对象)

ref 元素是 <constructor-arg/><property/> 定义元素内的最后一个元素。在这里,你将 Bean 的指定属性值设置为对容器管理的另一个 Bean(协作对象)的引用。被引用的 Bean 是要设置属性的 Bean 的依赖项,并且在设置属性之前根据需要按需进行初始化。

通过 <ref/> 标签的 bean 属性指定目标 Bean 是最通用的形式,它允许创建对同一容器或父容器中任何 Bean 的引用,无论它是否在同一个 XML 文件中。bean 属性的值可以与目标 Bean 的 id 属性相同,也可以与目标 Bean 的 name 属性中的任一值相同。

xml
<ref bean="someBean"/>

通过 parent 属性指定目标 Bean 会创建对当前容器的父容器中 Bean 的引用。你应该主要在拥有容器层次结构,并且希望用具有与父 Bean 相同名称的代理来包装父容器中的现有 Bean 时,使用此变体。

xml
<!-- 在父上下文中 -->
<bean id="accountService" class="com.something.SimpleAccountService">
	<!-- 在此处插入所需的依赖项 -->
</bean>
xml
<!-- 在子(后代)上下文中,Bean 名称与父 Bean 相同 -->
<bean id="accountService"
	class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target">
		<ref parent="accountService"/> <!-- 注意我们如何引用父 Bean -->
	</property>
</bean>

内部 Bean (Inner Beans)

<property/><constructor-arg/> 元素内部的 <bean/> 元素定义了一个内部 Bean:

xml
<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 中。

集合 (Collections)

<list/><set/><map/><props/> 元素分别设置 Java Collection 类型 ListSetMapProperties 的属性和参数。

xml
<bean id="moreComplexObject" class="example.ComplexObject">
	<!-- 导致调用 setAdminEmails(java.util.Properties) -->
	<property name="adminEmails">
		<props>
			<prop key="administrator">administrator@example.org</prop>
			<prop key="support">support@example.org</prop>
			<prop key="development">development@example.org</prop>
		</props>
	</property>
	<!-- 导致调用 setSomeList(java.util.List) -->
	<property name="someList">
		<list>
			<value>一个列表元素,后面跟着一个引用</value>
			<ref bean="myDataSource" />
		</list>
	</property>
	<!-- 导致调用 setSomeMap(java.util.Map) -->
	<property name="someMap">
		<map>
			<entry key="一个条目" value="只是某个字符串"/>
			<entry key="一个引用" value-ref="myDataSource"/>
		</map>
	</property>
	<!-- 导致调用 setSomeSet(java.util.Set) -->
	<property name="someSet">
		<set>
			<value>只是某个字符串</value>
			<ref bean="myDataSource" />
		</set>
	</property>
</bean>

Map 的键或值,或者 Set 的值,也可以是以下任何元素: bean | ref | idref | list | set | map | props | value | null

集合合并 (Collection Merging)

Spring 容器还支持合并集合。应用程序开发人员可以定义父级 <list/><map/><set/><props/> 元素,并让子级元素继承并覆盖父级集合中的值。

以下示例演示了集合合并:

xml
<beans>
	<bean id="parent" abstract="true" class="example.ComplexObject">
		<property name="adminEmails">
			<props>
				<prop key="administrator">administrator@example.com</prop>
				<prop key="support">support@example.com</prop>
			</props>
		</property>
	</bean>
	<bean id="child" parent="parent">
		<property name="adminEmails">
			<!-- 合并是在子集合定义上指定的 -->
			<props merge="true">
				<prop key="sales">sales@example.com</prop>
				<prop key="support">support@example.co.uk</prop>
			</props>
		</property>
	</bean>
<beans>

注意在 child Bean 定义的 adminEmails 属性的 <props/> 元素上使用了 merge="true" 属性。当 child Bean 由容器解析并实例化时,生成的实例具有一个 adminEmails Properties 集合,该集合包含将子级集合与父级集合合并后的结果。合并后的结果如下:

text
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

子级 Properties 集合继承了父级的所有元素,并且子级对 support 的值覆盖了父级集合中的值。这种合并行为同样适用于 <list/><map/><set/>

强类型集合

由于 Java 对泛型类型的支持,你可以使用强类型集合。如果使用 Spring 向 Bean 依赖注入强类型集合,你可以利用 Spring 的类型转换支持。

java
public class SomeClass {

	private Map<String, Float> accounts;

	public void setAccounts(Map<String, Float> accounts) {
		this.accounts = accounts;
	}
}
kotlin
class SomeClass {
	lateinit var accounts: Map<String, Float>
}
xml
<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 类型。

Null 和空字符串值

Spring 将属性等的空参数视为零长度的 String

xml
<bean class="ExampleBean">
	<property name="email" value=""/>
</bean>

上述配置等效于以下 Java 代码:exampleBean.setEmail("")

<null/> 元素用于处理 null 值:

xml
<bean class="ExampleBean">
	<property name="email">
		<null/>
	</property>
</bean>

上述配置等效于以下 Java 代码:exampleBean.setEmail(null)

p-命名空间快捷方式

p-命名空间允许你使用 bean 元素的属性(而不是嵌套的 <property/> 元素)来描述你的属性值或协作 Bean。

以下两个 XML 片段解析结果相同:

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 name="classic" class="com.example.ExampleBean">
		<property name="email" value="someone@somewhere.com"/>
	</bean>

	<bean name="p-namespace" class="com.example.ExampleBean"
		p:email="someone@somewhere.com"/>
</beans>

对于引用,可以使用 -ref 后缀:

xml
<bean name="john-modern"
	class="com.example.Person"
	p:name="John Doe"
	p:spouse-ref="jane"/>

c-命名空间快捷方式

类似于 p-命名空间,c-命名空间(在 Spring 3.1 中引入)允许使用内联属性配置构造函数参数,而不是嵌套的 constructor-arg 元素。

xml
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
	c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

如果参数名称不可用(由于没有 -parameters 标志),可以使用索引:

xml
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
	c:_2="something@somewhere.com"/>

复合属性名称

在设置 Bean 属性时,可以使用复合或嵌套的属性名称,只要路径中除了最后一个属性名之外的所有组件在 Bean 构建完成后都不为 null

xml
<bean id="something" class="things.ThingOne">
	<property name="fred.bob.sammy" value="123" />
</bean>

为了使上述代码工作,somethingfred 属性和 fredbob 属性在 Bean 构造后不得为 null


补充教学 —— 玩转 XML 配置的高级招式

1. idref 究竟有什么用? 很多人会纳闷:既然直接写 value="beanName" 就能生效,为什么还要费劲写 <idref bean="..."/>

  • 核心理由:提前纠错。如果你写成 value="wrongBeanName",Spring 在初始化时只把它当成一个普通字符串,直到你代码里真正去用这个名字找 Bean 时才发现报错。而 idref 会让 Spring 在启动容器时检查这个名字对应的 Bean 是否真的存在。
  • 应用场景:比如你要配置一个“处理器链”,每个节点需要知道下一个处理器的名字。

2. 内部 Bean 的封装性 内部 Bean (Inner Bean) 就像 Java 的匿名内部类。

  • 局部性:它没有 id,外界无法通过 getBean()ref 引用它。
  • 生命周期:它完全随外部 Bean 的生而生,随外部 Bean 的灭而灭。如果一个对象只为一个特定 Bean 服务且不希望被共享,内部 Bean 是最佳选择。

3. 集合合并 (Merge) 的实战价值 想象你有一个基础配置 Bean(父),定义了一组默认的系统管理员邮箱。

  • 不同的子模块可能有自己的管理员。
  • 通过 merge="true",你无需在子模块里重复列出那些默认邮箱,只需写模块特有的内容,Spring 会自动帮你拼成一个完整的列表。这在大型多模块系统的配置管理中非常推荐。

4. p:c: 命名空间的取舍

  • 优点:代码极度精简,看起来非常清晰。
  • 缺点:弱化了类型检查,且无法处理集合类型(必须退回到嵌套元素)。
  • 建议:在配置简单的属性(基本类型、字符串、单引用)时使用。如果逻辑复杂,还是用传统的 <property>

5. 复合属性的坑name="fred.bob.sammy" 这种写法虽然强大,但它预设了 fredbob 已经被初始化好了。如果你的 fred 是延迟加载的或者可能为 null,这行配置就会在启动时变成一颗炸弹。

Based on Spring Framework.