|
对于最新的稳定版本,请使用 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-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 IDEA 或 Eclipse 的 Spring 工具),否则拼写错误会在运行时而不是设计时被发现。这种 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="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>
第一种形式比第二种更可取,因为使用<code>0</code>标签可以让容器在部署时验证所引用的命名Bean是否确实存在。在第二种变体中,不会对传递给<code>1</code>属性的值进行验证,该属性属于<code>2</code> Bean。拼写错误只有在实际实例化<code>3</code> Bean时才会被发现(很可能造成严重后果)。如果<code>4</code> Bean是一个<a t="C5">原型</a> Bean,那么此拼写错误和由此引发的异常可能在容器部署很久之后才会被发现。
local 属性在 idref 元素上在 4.0 beans
XSD 中已不再支持,因为其不再提供比常规 bean 引用更多的价值。在升级到 4.0 模式时,请将现有的 idref local 引用更改为 idref bean。 |
一个常见的情况(至少在Spring 2.0之前的版本中)是<idref/>元素带来的价值,是在配置ProxyFactoryBean bean定义中的AOP拦截器时。在指定拦截器名称时使用<idref/>元素可以防止拼写错误的拦截器ID。
对其他Bean的引用(协作对象)
ref 元素是 <constructor-arg/> 或 <property/> 定义元素内的最后一个元素。在这里,您将指定的 bean 属性的值设置为容器管理的另一个 bean(协作对象)的引用。被引用的 bean 是要设置其属性的 bean 的依赖项,并且在属性设置之前按需进行初始化。(如果协作对象是一个单例 bean,它可能已经被容器初始化了。)所有的引用最终都是对另一个对象的引用。作用域和验证取决于您是否通过 bean 或 parent 属性指定了其他对象的 ID 或名称。
通过 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 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>
local 属性在 ref 元素上在 4.0 beans
XSD 中已不再支持,因为其不再提供比常规 bean 引用更多的价值。在升级到 4.0 模式时,请将现有的 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 或名称。如果已指定,容器不会将该值用作标识符。由于内部 bean 始终是匿名的,并且始终与外部 bean 一起创建,因此容器在创建时会忽略 scope 标志。无法独立访问内部 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容器还支持合并集合。应用程序开发人员可以定义一个父级<code>0</code>、<code>1</code>、<code>2</code>或<code>3</code>元素,并让子级<code>4</code>、<code>5</code>、<code>6</code>或<code>7</code>元素从父级集合继承并覆盖值。也就是说,子集合的值是父集合和子集合元素的合并结果,其中子集合元素会覆盖父集合中指定的值。
本节讨论了父-子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>
请注意在 merge=true 元素的 <props/> 属性中使用了 adminEmails bean 定义的 child 属性。当容器解析并实例化 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实例中的元素添加到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属性注入到something bean时,通过反射可以获得强类型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-namespace 的 XML 快捷方式
p-命名空间允许你使用 bean 元素的属性(而不是嵌套的 <property/> 元素)来描述你的属性值协作的 bean,或者两者都有。
Spring 支持可扩展的配置格式 使用命名空间,
这些格式基于 XML 模式定义。本章讨论的 beans 配置格式是在 XML 模式文档中定义的。然而,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>
此示例显示了p命名空间中的一个属性,名为email在bean定义中。
这告诉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-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命名空间的XML快捷方式
类似于 使用 p-命名空间的 XML 快捷方式,c-命名空间在 Spring 3.1 中引入,允许内联属性来配置构造函数参数,而不是嵌套的 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: 相同的约定(通过名称设置构造函数参数时在末尾添加 -ref)。同样,它需要在 XML 文件中声明,尽管它不在 XSD 模式中定义(它存在于 Spring 核心中)。
在构造函数参数名称不可用的极少数情况下(通常是在编译时未包含调试信息的字节码),你可以使用参数索引作为替代,如下所示:
<!-- 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。