Spring 依赖关系和配置详情

如上一章所述,您可以将 Bean 的属性和构造函数参数定义为对其他 Bean 的引用,或定义为内联值。为此,Spring 基于 XML 的配置元数据支持子元素类型 <property/> 和 <constructor-arg/> 元素。

直接值(原始类型、字符串等)

Spring 的 <property/> 元素的 value 属性用来将 Bean 的属性或构造函数参数指定为字符串值。Spring 的转换服务用于将这些值从字符串转换为 Bean 的属性或构造函数参数的实际类型。例如:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
   <!-- 调用 setDriverClassName(String) 方法设置 driverClassName 属性的值 -->
   <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(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>

注意,上面的示例是不是更简洁。不过,错误配置是在运行时而不是 Bean 配置时被发现,除非您使用的集成开发环境(如 IntelliJ IDEA 或 Spring Tools for Eclipse)在创建 Bean 定义时支持自动属性补全,强烈建议使用此类集成开发环境,提高开发效率,降低出错率。

您还可以配置一个 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 团队推荐的方式。

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 是否确实存在。在第二种变体中,不会对传递给客户机 Bean 的 targetName 属性的值进行验证。只有在实际实例化客户机 Bean 时,才会发现拼写错误(结果很可能是致命的)。如果客户机 Bean 是一个 prototype Bean,那么只有在容器部署后很久才会发现配置错误和由此产生异常。

提示:4.0 beans XSD 不再支持 idref 元素上的 local 属性,因为它不再提供比普通 bean 引用更多的值。在升级到 4.0 模式时,请将现有的 idref local 引用更改为 idref bean。

<idref/> 元素带来价值的一个常见地方(至少在 Spring 2.0 之前的版本中)是 ProxyFactoryBean Bean 定义中 AOP 拦截器的配置。在指定拦截器名称时使用 <idref/> 元素可以防止拼错拦截器 ID。

对其他 Bean 的引用

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

通过 <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。

下面展示了如何使用父属性:

<!-- 在父容器中 -->
<bean id="accountService" class="com.something.SimpleAccountService">
   <!-- 在此处插入所需的依赖项 -->
</bean>

<!-- 在子(子孙)容器中 -->
<!-- bean 名称与父 bean 相同 -->
<bean id="accountService"
   class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target">
       <!-- 注意我们是如何引用父 bean 的 -->
       <ref parent="accountService"/>
   </property>
   <!-- 根据需要在此处插入其他配置和依赖项 -->
</bean>

内部 Bean

如下面的示例所示,<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 中。

作为一种极端情况,可以从自定义作用域接收销毁回调 —— 例如,对于包含在单例 bean 中的请求作用域的内部 bean。内部 bean 实例的创建与它的包含 bean 绑定在一起,但是销毁回调允许它参与请求范围的生命周期。这种情况并不常见。内部 bean 通常只是共享其包含bean的作用域。

集合(Collection)

<list/>、<set/>、<map/> 和 <props/> 元素分别设置了 Java 集合类型 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">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>

Map 键、值或集合值的值也可以是以下任何元素:

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">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>

请注意,在子 Bean 定义的 adminEmails 属性的 <props/> 元素上使用了 merge=true 属性。当容器解析并实例化子 Bean 时,生成的实例将具有一个 adminEmails 属性集合,其中包含将子 Bean 的 adminEmails 集合与父 Bean 的 adminEmails 集合合并的结果。下面的列表显示了合并结果:

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

子属性集合的值集继承了父属性<props/>的所有属性元素,并且子属性的支持值覆盖了父属性集合中的值。

这种合并行为同样适用于 <list/>、<map/> 和 <set/> 集合类型。在 <list/> 元素的特定情况下,与列表集合类型相关的语义(即值的有序集合概念)将保持不变。父列表的值优先于子列表的所有值。而 Map、Set 和 Properties 集合类型则不存在排序。因此,对于作为容器内部使用的关联 Map、Set 和 Properties 实现类型的基础的集合类型,不存在排序语义。

集合合并的限制

注意,不能合并不同的集合类型(如 Map 和 List),如果尝试合并,将抛出相应的 "异常"(Exception)。

合并属性必须在较低的、继承的子定义上指定。在父级集合定义中指定合并属性是多余的,不会产生预期的合并效果。

强类型集合

得益于 Java 对泛型的支持,您可以使用强类型集合。也就是说,可以声明一个 Collection 类型,使其只能包含(例如)String 元素。如果您使用 Spring 将强类型集合依赖注入到 Bean 中,您就可以利用 Spring 的类型转换支持,在将强类型集合实例的元素添加到集合之前将其转换为相应的类型。例如:

public class SomeClass {
   // 声明了一个强类型Map
   private Map<String, Float> accounts;

   public void setAccounts(Map<String, Float> accounts) {
       this.accounts = accounts;
   }
}

对应的 XML 如下:

<beans>
   <bean id="something" class="x.y.SomeClass">
       <!-- 注入强类型 Map -->
       <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 将属性的空参数视为空字符串。以下基于 XML 的配置元数据片段将 email 属性设置为空字符串值("")。例如:

<bean class="ExampleBean">
   <property name="email" value=""/>
</bean>

上面示例的配置相当于下面的 Java 代码:

exampleBean.setEmail("");

<null/> 元素用于处理空值。例如:

<bean class="ExampleBean">
   <property name="email">
       <null/>
   </property>
</bean>

上面示例的配置相当于下面的 Java 代码:

exampleBean.setEmail(null);

XML 快捷方式:p命名空间

Spring 中,p 名称空间(p-namespace)允许您使用 Bean 元素的属性(而不是嵌套的 <property/> 元素)来描述属性值、协作 Bean 或两者。

Spring 支持基于 XML Schema 定义的带有命名空间的可扩展配置格式。本章讨论的 Bean 配置格式就是在 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">

   <!-- 使用标准格式,即嵌入 property 元素 -->
   <bean name="classic" class="com.example.ExampleBean">
       <property name="email" value="someone@somewhere.com"/>
   </bean>

   <!-- 使用 p 名称空间模式 -->
   <bean name="p-namespace" class="com.example.ExampleBean"
       p:email="someone@somewhere.com"/>
</beans>

该示例显示了 bean 定义中 p 命名空间中名为 email 的属性。这告诉 Spring 包含一个属性声明。如前所述,p 命名空间没有名称定义,因此您可以将 p 命名空间的属性的名称设置为 Bean 属性的名称,如:p:email 中的 email 属性即使 p 空间也是 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>

   <!-- p 命名空间使用 -->
   <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 的引用。

XML 快捷方式:c名称空间

与使用 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"/>

   <!-- 带可选参数名的传统声明 -->
   <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 命名空间声明 -->
   <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
       c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

c 命名空间使用与 p 命名空间相同的约定("ref "表示 Bean 引用)来通过名称设置构造函数参数。同样,即使它没有在 XSD 模式中定义(它存在于 Spring 核心中),也需要在 XML 文件中声明。

对于构造函数参数名称不可用的极少数情况(通常是在编译字节码时没有调试信息),可以使用参数索引,如下所示:

<!-- c 命名空间索引声明 -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
   c:_2="something@somewhere.com"/>

在实践中,构造函数解析机制在匹配参数方面相当有效,因此除非确实需要,我们建议在整个配置中使用名称符号。

复合属性名称

您可以在设置 Bean 属性时使用复合或嵌套属性名称,只要路径的所有组件 (最终属性名称除外) 都不为空。考虑以下 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 后,something 的 fred 属性和 fred 的 bob 属性不得为空。否则,将产生 NullPointerException 异常。

说说我的看法
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号