跳到主要内容
版本:7.0.3

详细依赖关系和配置

Hunyuan 7b 中英对照 Dependencies and Configuration in Detail

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

原始值(基本数据类型、字符串等)

<property/> 元素的 value 属性以人类可读的字符串形式指定一个属性或构造函数参数。Spring 的 转换服务 用于将这些字符串值转换为该属性或参数的实际类型。以下示例展示了各种值的设置方式:

<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-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://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>

</beans>

上述XML格式更为简洁。然而,拼写错误通常是在运行时才会被发现,而不是在设计阶段就发现的,除非你使用的是支持在创建Bean定义时自动完成属性的IDE(如IntelliJ IDEASpring 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的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 是一个 prototype Bean,那么这种拼写错误以及由此引发的异常可能要等到容器部署完成后很久才能被发现。

备注

至少在Spring 2.0之前的版本中,<idref/>元素常用于在ProxyFactoryBeanbean定义中的AOP拦截器配置中传递值。在指定拦截器名称时使用<idref/>元素可以避免拼写错误。

对其他Bean(协作者)的引用

ref元素是<constructor-arg/><property/>定义元素内的最后一个元素。在这里,你将一个bean的指定属性的值设置为对由容器管理的另一个bean(称为“协作者”)的引用。被引用的bean是需要设置该属性的bean的依赖项,并且在设置属性之前会根据需要进行初始化。(如果这个协作者是一个单例bean,它可能已经由容器初始化过了。)所有引用最终都是对另一个对象的引用。作用域和验证取决于你是通过bean属性还是parent属性来指定另一个对象的ID或名称。

通过<ref/>标签的bean属性来指定目标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>
备注

在4.0版本的beans XSD中,ref元素上的local属性不再被支持,因为它不再提供比普通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内的请求作用域(request-scoped)内部bean来说。内部bean实例的创建与其包含bean相关联,但销毁回调使其能够参与请求作用域(request scope)的生命周期。这种情况并不常见。通常情况下,内部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">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</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">administrator@example.com</prop>
<prop key="support">support@example.com</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">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集合,该集合包含了将childadminEmails集合与父级的adminEmails集合合并后的结果。以下列表显示了合并后的结果:

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

子元素Properties集合的值集继承了父元素<props/>中的所有属性元素,而子元素中support属性的值会覆盖父集合中的该值。

这种合并行为同样适用于 <list/><map/><set/> 集合类型。在 <list/> 元素的具体情况下,与 List 集合类型相关的语义(即值的“有序”集合概念)得以保留。父元素的值会出现在所有子元素值的前面。而对于 MapSetProperties 集合类型,则不存在任何排序。因此,对于容器内部使用的 MapSetProperties 实现类型所基于的这些集合类型而言,没有排序语义起作用。

集合合并的局限性

你不能合并不同类型的集合(例如 MapList)。如果你尝试这样做,将会抛出相应的 Exception。必须在较低级别的、继承来的、子级集合定义上指定 merge 属性。在父级集合定义上指定 merge 属性是多余的,并且不会实现预期的合并效果。

强类型集合

得益于Java对泛型类型的支持,你可以使用强类型集合。也就是说,你可以声明一种Collection类型,使其只能包含(例如)String类型的元素。如果你使用Spring通过依赖注入将一个强类型的Collection注入到Bean中,那么就可以利用Spring的类型转换功能,在元素被添加到Collection之前将其转换为适当的类型。以下Java类和Bean定义展示了如何实现这一点:

public class SomeClass {

private Map<String, Float> accounts;

public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<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>

somethingbean的accounts属性准备进行注入时,可以通过反射获取到强类型Map<String, Float>中元素类型的泛型信息。因此,Spring的类型转换基础设施能够识别出这些值元素的类型为Float,而字符串值(“9.99”、“2.75”和“3.99”)会被转换为实际的Float类型。

空值和空字符串值

Spring 将属性等为空的参数视为空的 String。以下基于 XML 的配置元数据片段将 email 属性设置为空的 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-namespace的XML快捷方式

p-namespace允许你使用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="someone@somewhere.com"/>
</bean>

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

示例中展示了在bean定义中p-namespace下名为email的属性。这告诉Spring需要包含一个属性声明。如前所述,p-namespace没有模式定义(schema definition),因此你可以将属性名称设置为该属性的实际名称。

下一个示例中包含了另外两个bean定义,这两个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-namespace 的属性值,还使用了一种特殊的格式来声明属性引用。第一个 Bean 定义使用 <property name="spouse" ref="jane"/> 来创建从 bean john 到 bean jane 的引用,而第二个 Bean 定义则使用 p:spouse-ref="jane" 作为属性来实现相同的功能。在这里,spouse 是属性名,而 -ref 部分表示这不是一个直接的值,而是一个对另一个 Bean 的引用。

备注

p-命名空间不像标准的XML格式那样灵活。例如,声明属性引用的格式与以Ref结尾的属性发生冲突,而标准XML格式则没有这种问题。我们建议您仔细选择使用的方法,并与团队成员沟通,以避免生成同时使用这三种方法的XML文档。

使用 c-namespace 的 XML 快捷方式

类似于p-namespace的XML快捷方式,在Spring 3.1中引入的c-namespace允许使用内联属性来配置构造函数参数,而无需使用嵌套的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="something@somewhere.com"/>
</bean>

<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</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="something@somewhere.com"/>
备注

由于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 的属性,fred 又拥有一个名为 bob 的属性,而 bob 再拥有一个名为 sammy 的属性,最终的 sammy 属性被设置为值 123。为了使这一过程能够正常工作,在 bean 被构造完成后,somethingfred 属性和 fredbob 属性都不能为 null。否则,将会抛出 NullPointerException