依赖项和配置详情
如上一节所述,您可以将 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://: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://:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
上述 XML 更简洁。但是,除非您使用支持创建 bean 定义时自动完成属性的 IDE(例如 IntelliJ IDEA 或 Spring 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://: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 实际实例化时才会被发现(很可能导致致命结果)。如果 client bean 是一个原型 bean,那么这种拼写错误和由此产生的异常可能在容器部署很久之后才会被发现。
至少在 Spring 2.0 之前的版本中,<idref/> 元素发挥作用的一个常见地方是在 ProxyFactoryBean bean 定义中配置 AOP 拦截器。在指定拦截器名称时使用 <idref/> 元素可以防止您拼错拦截器 ID。 |
对其他 Bean 的引用(协作者)
ref 元素是 <constructor-arg/> 或 <property/> 定义元素中的最后一个元素。在这里,您将 bean 的指定属性的值设置为对容器管理的另一个 bean(协作者)的引用。引用的 bean 是要设置其属性的 bean 的依赖项,它会在设置属性之前根据需要按需初始化。(如果协作者是一个单例 bean,它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是通过 bean 还是 parent 属性指定其他对象的 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 name is the same as the parent bean -->
<bean id="accountService"
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 类型 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>
映射键或值,或集合值的值,也可以是以下任何元素
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 集合类型关联的语义(即有序值集合的概念)得以保留。父级的值位于所有子列表值之前。在 Map、Set 和 Properties 集合类型的情况下,不存在排序。因此,对于容器内部使用的关联 Map、Set 和 Properties 实现类型所基于的集合类型,没有排序语义生效。
集合合并的限制
您不能合并不同的集合类型(例如 Map 和 List)。如果您尝试这样做,将抛出适当的 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.99、2.75 和 3.99)转换为实际的 Float 类型。
Null 和空字符串值
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 定义中一个名为 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 核心中)。
在极少数情况下,如果构造函数参数名称不可用(通常是如果字节码在没有 -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。为了使其正常工作,something 的 fred 属性和 fred 的 bob 属性在 bean 构造后不得为 null。否则,将抛出 NullPointerException。