依赖项和配置详解

上一节 所述,您可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用或内联定义的值。 Infra 基于 XML 的配置元数据在其 <property/><constructor-arg/> 元素内支持子元素类型以实现此目的。

直接值(基本类型、字符串等)

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

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <!-- 导致 setDriverClassName(String) 调用 -->
  <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 IDEAInfra Tools for Eclipse),否则拼写错误是在运行时而不是设计时发现的。 强烈建议使用此类 IDE 辅助。

您还可以配置 java.util.Properties 实例,如下所示:

<bean id="mappings"
  class="infra.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>

Infra 容器通过使用 JavaBeans PropertyEditor 机制将 <value/> 元素内的文本转换为 java.util.Properties 实例。 这是一个很好的快捷方式,也是 Infra 团队偏爱使用嵌套 <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 实际上是否存在。 在第二种变体中,不对传递给 client bean 的 targetName 属性的值执行验证。 只有在实际实例化 client bean 时才会发现拼写错误(最有可能导致致命结果)。 如果 client bean 是 原型 bean,则此拼写错误和产生的异常可能仅在部署容器很久之后才被发现。

idref 元素上的 local 属性在 4.0 beans XSD 中不再受支持,因为它不再提供超过常规 bean`引用的价值。 升级到 4.0 模式时,将现有的 `idref local 引用更改为 idref bean

<idref/> 元素带来价值的一个常见地方(至少在 Infra 2.0 之前的版本中)是在 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 属性:

<!-- 在父上下文中 -->
<bean id="accountService" class="com.something.SimpleAccountService">
  <!-- 根据需要在此处插入依赖项 -->
</bean>
<!-- 在子(后代)上下文中 -->
<bean id="accountService" <!-- bean 名称与父 bean 相同 -->
  class="infra.aop.framework.ProxyFactoryBean">
  <property name="target">
    <ref parent="accountService"/> <!-- 注意我们如何引用父 bean -->
  </property>
  <!-- 根据需要在此处插入其他配置和依赖项 -->
</bean>
ref 元素上的 local 属性在 4.0 beans XSD 中不再受支持,因为它不再提供超过常规 bean 引用的价值。 升级到 4.0 模式时,将现有的 ref local 引用更改为 ref 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 的作用域。

集合

<list/><set/><map/><props/> 元素分别设置 Java Collection 类型 ListSetMapProperties 的属性和参数。 以下示例显示了如何使用它们:

<bean id="moreComplexObject" class="example.ComplexObject">
  <!-- 导致 setAdminEmails(java.util.Properties) 调用 -->
  <property name="adminEmails">
    <props>
      <prop key="administrator">[email protected]</prop>
      <prop key="support">[email protected]</prop>
      <prop key="development">[email protected]</prop>
    </props>
  </property>
  <!-- 导致 setSomeList(java.util.List) 调用 -->
  <property name="someList">
    <list>
      <value>a list element followed by a reference</value>
      <ref bean="myDataSource" />
    </list>
  </property>
  <!-- 导致 setSomeMap(java.util.Map) 调用 -->
  <property name="someMap">
    <map>
      <entry key="an entry" value="just some string"/>
      <entry key="a ref" value-ref="myDataSource"/>
    </map>
  </property>
  <!-- 导致 setSomeSet(java.util.Set) 调用 -->
  <property name="someSet">
    <set>
      <value>just some string</value>
      <ref bean="myDataSource" />
    </set>
  </property>
</bean>

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

bean | ref | idref | list | set | map | props | value | null

集合合并

Infra 容器还支持合并集合。应用程序开发人员可以定义父 <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">
      <!-- 合并在子集合定义上指定 -->
      <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 集合的概念)得以保留。 父项的值位于所有子列表的值之前。 在 MapSetProperties 集合类型的情况下,不存在排序。 因此,对于容器内部使用的关联 MapSetProperties 实现类型所依赖的集合类型,没有排序语义生效。

集合合并的局限性

您不能合并不同的集合类型(例如 MapList)。如果您尝试这样做,将抛出适当的 Exceptionmerge 属性必须在较低的、继承的子定义上指定。在父集合定义上指定 merge 属性是多余的,并且不会导致所需的合并。

强类型集合

由于 Java 对泛型类型的支持,您可以使用强类型集合。 也就是说,可以声明一种 Collection 类型,使其只能包含(例如)String 元素。 如果您使用 Infra 将强类型 Collection 依赖注入到 bean 中,您可以利用 Infra 类型转换支持, 以便您的强类型 Collection 实例的元素在添加到 Collection 之前转换为适当的类型。 以下 Java 类和 bean 定义显示了如何执行此操作:

  • Java

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>

当准备注入 something bean 的 accounts 属性时,关于强类型 Map<String, Float> 元素类型的泛型信息可以通过反射获得。 因此,Infra 类型转换基础设施将各种值元素识别为 Float 类型,并将字符串值(9.992.753.99)转换为实际的 Float 类型。

Null 和空字符串值

Infra 将属性等的空参数视为空 Strings。 以下基于 XML 的配置元数据片段将 email 属性设置为空 String 值 ("")。

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

前面的示例等效于以下 Java 代码:

  • Java

exampleBean.setEmail("");

<null/> 元素处理 null 值。以下清单显示了一个示例:

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

前面的配置等效于以下 Java 代码:

  • Java

exampleBean.setEmail(null);

使用 p-namespace 的 XML 快捷方式

p-namespace 允许您使用 bean 元素的属性(而不是嵌套的 <property/> 元素)来描述您的属性值协作 bean,或两者兼而有之。

Infra 支持 带有命名空间 的可扩展配置格式,这些格式基于 XML Schema 定义。 本章讨论的 beans 配置格式是在 XML Schema 文档中定义的。 但是,p-namespace 未在 XSD 文件中定义,仅存在于 Infra 核心中。

以下示例显示了解析为相同结果的两个 XML 片段(第一个使用标准 XML 格式,第二个使用 p-namespace):

<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-namespace 中名为 email 的属性。 这告诉 Infra 包含一个属性声明。如前所述,p-namespace 没有模式定义,因此您可以将属性名称设置为属性名称。

下一个示例包括另外两个 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-namespace 不如标准 XML 格式灵活。例如,声明属性引用的格式与以 Ref 结尾的属性冲突,而标准 XML 格式则不会。 我们建议您仔细选择您的方法,并将其传达给您的团队成员,以避免生成同时使用所有三种方法的 XML 文档。

使用 c-namespace 的 XML 快捷方式

类似于 使用 p-namespace 的 XML 快捷方式, Infra 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"/>

  <!-- 带有可选参数名称的传统声明 -->
  <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 声明 -->
  <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 模式中定义(它存在于 Infra 核心内),也需要在 XML 文件中声明它。

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

<!-- c-namespace 索引声明 -->
<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。 为了使其工作,somethingfred 属性和 fredbob 属性在构造 bean 后必须不为 null。 否则,将抛出 NullPointerException