依赖与配置详解 (Dependencies and Configuration in Detail)
正如前一节所述,你可以将 Bean 的属性和构造函数参数定义为对其他受管 Bean(协作对象)的引用,或者定义为内联定义的值。为此,Spring 的基于 XML 的配置元数据支持在其 <property/> 和 <constructor-arg/> 元素中使用子元素类型。
直接值 (基本类型、字符串等)
<property/> 元素的 value 属性将属性或构造函数参数指定为可读的字符串表示形式。Spring 的转换服务 (Conversion Service) 用于将这些值从 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 更为简洁。然而,除非你使用支持自动属性完成的 IDE(如 IntelliJ IDEA 或 Spring Tools),否则拼写错误是在运行时而不是设计时发现的。强烈建议使用此类 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="collaborator" class="..." />
<bean id="client" class="...">
<property name="targetName">
<idref bean="collaborator" />
</property>
</bean>上述 Bean 定义片段在运行时与以下片段完全等效:
<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 属性中的任一值相同。
<ref bean="someBean"/>通过 parent 属性指定目标 Bean 会创建对当前容器的父容器中 Bean 的引用。你应该主要在拥有容器层次结构,并且希望用具有与父 Bean 相同名称的代理来包装父容器中的现有 Bean 时,使用此变体。
<!-- 在父上下文中 -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- 在此处插入所需的依赖项 -->
</bean><!-- 在子(后代)上下文中,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:
<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 类型 List、Set、Map 和 Properties 的属性和参数。
<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/> 元素,并让子级元素继承并覆盖父级集合中的值。
以下示例演示了集合合并:
<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 集合,该集合包含将子级集合与父级集合合并后的结果。合并后的结果如下:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk子级 Properties 集合继承了父级的所有元素,并且子级对 support 的值覆盖了父级集合中的值。这种合并行为同样适用于 <list/>、<map/> 和 <set/>。
强类型集合
由于 Java 对泛型类型的支持,你可以使用强类型集合。如果使用 Spring 向 Bean 依赖注入强类型集合,你可以利用 Spring 的类型转换支持。
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 将属性等的空参数视为零长度的 String。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>上述配置等效于以下 Java 代码:exampleBean.setEmail("")。
<null/> 元素用于处理 null 值:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>上述配置等效于以下 Java 代码:exampleBean.setEmail(null)。
p-命名空间快捷方式
p-命名空间允许你使用 bean 元素的属性(而不是嵌套的 <property/> 元素)来描述你的属性值或协作 Bean。
以下两个 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 后缀:
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>c-命名空间快捷方式
类似于 p-命名空间,c-命名空间(在 Spring 3.1 中引入)允许使用内联属性配置构造函数参数,而不是嵌套的 constructor-arg 元素。
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>如果参数名称不可用(由于没有 -parameters 标志),可以使用索引:
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>复合属性名称
在设置 Bean 属性时,可以使用复合或嵌套的属性名称,只要路径中除了最后一个属性名之外的所有组件在 Bean 构建完成后都不为 null。
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>为了使上述代码工作,something 的 fred 属性和 fred 的 bob 属性在 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" 这种写法虽然强大,但它预设了 fred 和 bob 已经被初始化好了。如果你的 fred 是延迟加载的或者可能为 null,这行配置就会在启动时变成一颗炸弹。