|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
依赖项与配置详解
如上一节所述,您可以将 bean 的属性和构造函数参数定义为对其他受管 bean(协作者)的引用,或定义为内联值。Spring 基于 XML 的配置元数据为此目的在其 <property/> 和 <constructor-arg/> 元素中支持子元素类型。
直接值(基本类型、字符串等)
value 元素的 <property/> 属性以人类可读的字符串形式指定一个属性或构造函数参数。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-命名空间 来实现更加简洁的 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 更加简洁。然而,除非你使用支持在创建 bean 定义时自动完成属性的 IDE(例如 IntelliJ IDEA 或 Eclipse 的 Spring Tools),否则拼写错误只能在运行时而非设计时被发现。强烈推荐使用此类 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 的 <value/> 机制,将 java.util.Properties 元素内的文本转换为一个 PropertyEditor 实例。这是一种便捷的简写方式,也是 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 是否确实存在。而在第二种形式中,传递给 targetName bean 的 client 属性的值不会经过任何验证。拼写错误只有在实际实例化 client bean 时才会被发现(很可能会导致严重后果)。如果 client bean 是一个 原型(prototype) bean,那么这种拼写错误及其引发的异常可能在容器部署很久之后才会被发现。
在 4.0 版本的 beans XSD 中,local 元素上的 idref 属性不再受支持,因为它相较于普通的 bean 引用已不再提供额外价值。在升级到 4.0 schema 时,请将现有的 idref local 引用改为 idref bean。 |
一个常见使用 <idref/> 元素带来价值的场景(至少在 Spring 2.0 之前的版本中)是在 xref page 的 bean 定义中配置 AOP 拦截器。当你指定拦截器名称时使用 <idref/> 元素,可以防止拼错拦截器 ID。
对其他 Bean 的引用(协作者)
ref 元素是 <constructor-arg/> 或 <property/>
定义元素内部的最后一个元素。在这里,您将某个 bean 的指定属性值设置为对容器所管理的另一个 bean(即协作者)的引用。
被引用的 bean 是当前 bean(其属性将被设置)的一个依赖项,并会在设置该属性之前按需进行初始化。(如果该协作者是一个单例 bean,则可能已经被容器初始化过了。)
所有引用最终都是对另一个对象的引用。其作用域和验证取决于您是通过 bean 属性还是 parent 属性来指定目标对象的 ID 或名称。
通过 bean 标签的 <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>
在 4.0 版本的 beans XSD 中,local 元素上的 ref 属性不再受支持,因为它相较于普通的 bean 引用已不再提供额外价值。在升级到 4.0 schema 时,请将现有的 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 类型 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>
映射(map)的键或值,或者集合(set)的值,也可以是以下任意元素:
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值集合的概念)得以保留。父级的值位于所有子级列表的值之前。对于 Map、Set 和 Properties 集合类型,不存在顺序。因此,对于容器内部使用的关联 Map、Set 和 Properties 实现类型所基于的集合类型,不生效任何顺序语义。
集合合并的局限性
您不能合并不同的集合类型(例如 Map 和 List)。如果您尝试这样做,系统会抛出相应的 Exception。必须在较低层级的、继承的子定义上指定 merge 属性。在父集合定义上指定 merge 属性是多余的,不会产生预期的合并效果。
强类型集合
得益于 Java 对泛型类型的支持,你可以使用强类型的集合。
也就是说,可以声明一个 Collection 类型,使其只能包含(例如)String 元素。
如果你使用 Spring 将一个强类型的 Collection 依赖注入到某个 bean 中,就可以利用 Spring 的类型转换支持,
使得在将元素添加到 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>
当 accounts bean 的 something 属性准备进行注入时,通过反射可以获取强类型 Map<String, Float> 中元素类型的泛型信息。因此,Spring 的类型转换基础设施能够识别各个值元素的类型为 Float,并将字符串值(9.99、2.75 和 3.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 Schema 定义。beans 配置格式(本章所讨论的)是在一个 XML Schema 文档中定义的。
然而,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 定义中使用 p-命名空间的一个名为 email 的属性。
这会告诉 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 schema 中定义(它存在于 Spring 核心内部),也需要在 XML 文件中进行声明。
对于极少数构造函数参数名称不可用的情况(通常是因为字节码在编译时未包含调试信息),你可以退而使用参数索引,如下所示:
<!-- 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 构造完成后,
something 的 fred 属性以及 fred 的 bob 属性不得为 null。否则,将抛出 NullPointerException。