5.4.2.依赖配置详解

正如前面章节所提到的,bean的属性及构造器参数既可以引用容器中的其他bean,也可以是内联(inline)bean。在spring的XML配置中使用<property/><constructor-arg/>元素定义。

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

<property/>元素中的<value/>元素通过人可以理解的字符串来指定属性或构造器参数的值。Spring的转换器将用于把字符串从java.lang.String类型转化为实际的属性或参数类型。

  1. <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  2. <!-- results in a setDriverClassName(String) call -->
  3. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  4. <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
  5. <property name="username" value="root"/>
  6. <property name="password" value="masterkaoli"/>
  7. </bean>

接下来我们采用p命名空间来演示上面的代码:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:p="http://www.springframework.org/schema/p"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
  7. destroy-method="close"
  8. p:driverClassName="com.mysql.jdbc.Driver"
  9. p:url="jdbc:mysql://localhost:3306/mydb"
  10. p:username="root"
  11. p:password="masterkaoli"/>
  12. </beans>

上面这种形式似乎更有效率一点;当然我们也可以按照下面这种方式配置一个java.util.Properties实例:

  1. <bean id="mappings"
  2. class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  3. <!-- typed as a java.util.Properties -->
  4. <property name="properties">
  5. <value>
  6. jdbc.driver.className=com.mysql.jdbc.Driver
  7. jdbc.url=jdbc:mysql://localhost:3306/mydb
  8. </value>
  9. </property>
  10. </bean>

看到什么了吗?如果采用上面的配置,Spring容器将使用JavaBean PropertyEditor把<value/>元素中的文本转换为一个java.util.Properties实例。由于这种做法的简单,因此Spring团队在很多地方也会采用内嵌的<value/>元素来代替value属性。

idref元素

idref元素用来将容器内其它bean的id传给<constructor-arg/><property/>元素,同时提供错误验证功能。

  1. <bean id="theTargetBean" class="..."/>
  2. <bean id="theClientBean" class="...">
  3. <property name="targetName">
  4. <idref bean="theTargetBean" />
  5. </property>
  6. </bean>

上述bean定义片段完全地等同于(在运行时)以下的片段:

  1. <bean id="theTargetBean" class="..." />
  2. <bean id="client" class="...">
  3. <property name="targetName" value="theTargetBean" />
  4. </bean>

第一种形式比第二种更可取的主要原因是,使用idref标记允许容器在部署时 验证所被引用的bean是否存在。而第二种方式中,传给client bean的targetName属性值并没有被验证。任何的输入错误仅在client bean实际实例化时才会被发现(可能伴随着致命的错误)。如果client bean 是prototype类型的bean,则此输入错误(及由此导致的异常)可能在容器部署很久以后才会被发现。

  1. `idref`中的`local`属性已经从4.0xsd移除,如果你要从低版本升级到4.0的话,请使用`idref`中的`bean`替代。

上面的例子中,与在ProxyFactoryBean bean定义中使用<idref/>元素指定AOP interceptor的相同之处在于:如果使用<idref/>元素指定拦截器名字,可以避免因一时疏忽导致的拦截器ID拼写错误。

引用其它的bean(协作者)

<constructor-arg/><property/>元素内部还可以使用ref元素。该元素用来将bean中指定属性的值设置为对容器中的另外一个bean的引用。如前所述,该引用bean将被作为依赖注入,而且在注入之前会被初始化(如果是singleton bean则已被容器初始化)。尽管都是对另外一个对象的引用,但是通过id/name指向另外一个对象却有三种不同的形式,不同的形式将决定如何处理作用域及验证。

第一种形式也是最常见的形式是通过使用<ref/>标记指定bean属性的目标bean,通过该标签可以引用同一容器或父容器内的任何bean(无论是否在同一XML文件中)。XML ‘bean’元素的值既可以是指定bean的id值也可以是其name值。

  1. <ref bean="someBean"/>

第二种形式是使用ref的local属性指定目标bean,这种方式现在更改为ref bean而不再是ref local(4.0开始)

第三种方式是通过使用ref的parent属性来引用当前容器的父容器中的bean。parent属性值既可以是目标bean的id值,也可以是name属性值。而且目标bean必须在当前容器的父容器中。使用parent属性的主要用途是为了用某个与父容器中的bean同名的代理来包装父容器中的一个bean(例如,子上下文中的一个bean定义覆盖了他的父bean)。

  1. <!-- in the parent context -->
  2. <bean id="accountService" class="com.foo.SimpleAccountService">
  3. <!-- insert dependencies as required as here -->
  4. </bean>
  1. <!-- in the child (descendant) context -->
  2. <bean id="accountService" <!-- bean name is the same as the parent bean -->
  3. class="org.springframework.aop.framework.ProxyFactoryBean">
  4. <property name="target">
  5. <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
  6. </property>
  7. <!-- insert other configuration and dependencies as required here -->
  8. </bean>

内部bean

所谓的内部bean(inner bean)是指在一个bean的<property/><constructor-arg/>元素中使用<bean/>元素定义的bean。内部bean定义不需要有id或name属性,即使指定id 或 name属性值也将会被容器忽略。

  1. <bean id="outer" class="...">
  2. <!-- instead of using a reference to a target bean, simply define the target bean inline -->
  3. <property name="target">
  4. <bean class="com.example.Person"> <!-- this is the inner bean -->
  5. <property name="name" value="Fiona Apple"/>
  6. <property name="age" value="25"/>
  7. </bean>
  8. </property>
  9. </bean>

注意:内部bean中的scope标记及id或name属性将被忽略。内部bean总是匿名的且它们总是prototype模式的。同时将内部bean注入到包含该内部bean之外的bean是不可能的。

集合

通过<list/><set/><map/><props/>元素可以定义和设置与Java Collection类型对应List、Set、Map及Properties的值。

  1. <bean id="moreComplexObject" class="example.ComplexObject">
  2. <!-- results in a setAdminEmails(java.util.Properties) call -->
  3. <property name="adminEmails">
  4. <props>
  5. <prop key="administrator">administrator@example.org</prop>
  6. <prop key="support">support@example.org</prop>
  7. <prop key="development">development@example.org</prop>
  8. </props>
  9. </property>
  10. <!-- results in a setSomeList(java.util.List) call -->
  11. <property name="someList">
  12. <list>
  13. <value>a list element followed by a reference</value>
  14. <ref bean="myDataSource" />
  15. </list>
  16. </property>
  17. <!-- results in a setSomeMap(java.util.Map) call -->
  18. <property name="someMap">
  19. <map>
  20. <entry key="an entry" value="just some string"/>
  21. <entry key ="a ref" value-ref="myDataSource"/>
  22. </map>
  23. </property>
  24. <!-- results in a setSomeSet(java.util.Set) call -->
  25. <property name="someSet">
  26. <set>
  27. <value>just some string</value>
  28. <ref bean="myDataSource" />
  29. </set>
  30. </property>
  31. </bean>

注意:map的key或value值,或set的value值还可以是以下元素:

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

集合的合并

Spring IoC容器将支持集合的合并。这样我们可以定义parent-style和child-style的<list/><map/><set/><props/>元素,子集合的值从其父集合继承和覆盖而来;也就是说,父子集合元素合并后的值就是子集合中的最终结果,而且子集合中的元素值将覆盖父集全中对应的值。

请注意,关于合并的这部分利用了parent-child bean机制。此内容将在后面介绍。

下面的例子展示了集合合并特性:

  1. <beans>
  2. <bean id="parent" abstract="true" class="example.ComplexObject">
  3. <property name="adminEmails">
  4. <props>
  5. <prop key="administrator">administrator@example.com</prop>
  6. <prop key="support">support@example.com</prop>
  7. </props>
  8. </property>
  9. </bean>
  10. <bean id="child" parent="parent">
  11. <property name="adminEmails">
  12. <!-- the merge is specified on the child collection definition -->
  13. <props merge="true">
  14. <prop key="sales">sales@example.com</prop>
  15. <prop key="support">support@example.co.uk</prop>
  16. </props>
  17. </property>
  18. </bean>
  19. <beans>

在上面的例子中,childbean的adminEmails属性的··元素上使用了merge=true属性。当child bean被容器实际解析及实例化时,其adminEmails将与父集合的adminEmails属性进行合并。

  1. administrator=administrator@example.com
  2. sales=sales@example.com
  3. support=support@example.co.uk

注意到这里子bean的Properties集合将从父<props/>继承所有属性元素。同时子bean的support值将覆盖父集合的相应值。

对于<list/><map/><set/>集合类型的合并处理都基本类似,在某个方面<list/>元素比较特殊,这涉及到List集合本身的语义学,就拿维护一个有序集合中的值来说,父bean的列表内容将排在子bean列表内容的前面。对于Map、Set及Properties集合类型没有顺序的概念,因此作为相关的Map、Set及Properties实现基础的集合类型在容器内部没有排序的语义。

集合合并的限制

你不能合并不同类型的集合(如Map跟List),如果你尝试这么做的话,将会抛出异常。

强类型集合

泛型在Java5中被引入,这样你就可以使用强类型集合。比如,声明一个只能包含String类型元素的Collection。假若使用Spring来给bean注入强类型的Collection,那就可以利用Spring的类型转换能,当向强类型Collection中添加元素前,这些元素将被转换。

  1. public class Foo {
  2. private Map<String, Float> accounts;
  3. public void setAccounts(Map<String, Float> accounts) {
  4. this.accounts = accounts;
  5. }
  6. }
  1. <beans>
  2. <bean id="foo" class="x.y.Foo">
  3. <property name="accounts">
  4. <map>
  5. <entry key="one" value="9.99"/>
  6. <entry key="two" value="2.75"/>
  7. <entry key="six" value="3.99"/>
  8. </map>
  9. </property>
  10. </bean>
  11. </beans>

在foo bean的accounts属性被注入之前,通过反射,利用强类型Map<String, Float>的泛型信息,Spring的底层类型转换机制将会把各种value元素值转换为Float类型,因此字符串9.99、2.75及3.99就会被转换为实际的Float类型。

Null与空字符串

properties中的空参数,Spring将会当做空字符串,如下:

  1. <bean class="ExampleBean">
  2. <property name="email" value=""/>
  3. </bean>

以上代码等同于如下的Java代码:

  1. exampleBean.setEmail("")

<null/>也会作为null处理:

  1. <bean class="ExampleBean">
  2. <property name="email">
  3. <null/>
  4. </property>
  5. </bean>

同:

  1. exampleBean.setEmail(null)

简洁的XML配置-p命名空间

我们现在可以使用P命名空间来简化我们的配置:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:p="http://www.springframework.org/schema/p"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean name="classic" class="com.example.ExampleBean">
  7. <property name="email" value="foo@bar.com"/>
  8. </bean>
  9. <bean name="p-namespace" class="com.example.ExampleBean"
  10. p:email="foo@bar.com"/>
  11. </beans>

上面的代码中,我们用p:email替代了property name=”email”,接下来演示其他情况:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:p="http://www.springframework.org/schema/p"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean name="john-classic" class="com.example.Person">
  7. <property name="name" value="John Doe"/>
  8. <property name="spouse" ref="jane"/>
  9. </bean>
  10. <bean name="john-modern"
  11. class="com.example.Person"
  12. p:name="John Doe"
  13. p:spouse-ref="jane"/>
  14. <bean name="jane" class="com.example.Person">
  15. <property name="name" value="Jane Doe"/>
  16. </bean>
  17. </beans>

在上面的代码中,我们使用了特殊的格式来声明引用关系“p:spouse-ref=”jane””等同于<property name="spouse" ref="jane"/>

简洁的XML配置-c命名空间

我可以用c命名空间来简化构造器的参数传递,过程类似于p命名空间:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:c="http://www.springframework.org/schema/c"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="bar" class="x.y.Bar"/>
  7. <bean id="baz" class="x.y.Baz"/>
  8. <!-- traditional declaration -->
  9. <bean id="foo" class="x.y.Foo">
  10. <constructor-arg ref="bar"/>
  11. <constructor-arg ref="baz"/>
  12. <constructor-arg value="foo@bar.com"/>
  13. </bean>
  14. <!-- c-namespace declaration -->
  15. <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
  16. </beans>

另外还可以通过指定下标来传递:

  1. <!-- c-namespace index declaration -->
  2. <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

组合属性名称

当设置bean的组合属性时,除了最后一个属性外,只要其他属性值不为null,组合或嵌套属性名是完全合法的。例如,下面bean的定义:

  1. <bean id="foo" class="foo.Bar">
  2. <property name="fred.bob.sammy" value="123" />
  3. </bean>

foo bean有个fred属性,此属性有个bob属性,而bob属性又有个sammy属性,最后把sammy属性设置为123。为了让此定义能工作,foo的fred属性及fred的bob属性在bean被构造后都必须非空,否则将抛出NullPointerException异常。