标记:  spring , 依赖注入 , ioc

    接上一篇 spring 依赖注入 ,讲解了spring依赖注入的方式(构造器注入,set注入,静态工厂注入和实例工厂注入),这一次我们深入到spring依赖注入配置文件去。

    在 sprint 依赖注入方式上,可以知道是使用<property /> 和<constructor-arg /> 来注入依赖对象和基本值(基本数据类型和String值)。现在我们将详细谈一下各种值的配置方式。

一、基本值(基本数据类型和String)

    在xml配置文件里,<property />使用属性”value“来指定参数的值。顺带提一下的是,javaBeans 里的PropertyEditors 会将这些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="123"/>
</bean>

    现在来看一下一种更加简洁的配置,使用 p 命名空间,如下:

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     <!-- 引入p 命名空间 –>
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://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="masterkaoli"/>
</beans>

    使用 p 命名空间简化了配置,可也有一定的不足之处,就是需要再运行时才能发现拼写错误,而不是在设计时就可以知道。当然了,如果使用 IDEA 或者 springSource 这些具有代码提示功能的IDE,也完全可以避免这些避免错误。其实,还有另外一种方式配置 property,如下配置java.util.ProPerties:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 <property name="properties">
    <value>
       jdbc.driver.className=com.mysql.jdbc.Driver
       jdbc.url=jdbc:mysql://localhost:3306/mydb
    </value>
 </property>
</bean>

    如上配置是值得倡导的,使用<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提供了容器在部署时对“theTargetBean”的验证(确定它是否存在)。如果使用第二种方式,则需要到client被初始化时才可以发现,特别当这个 client 是 prototype,需要等容器初始化之后很长一段时间才能被发现。

    另外,当索引的 bean也在同一个xml配置文件时,可以使用属性 local,有助于xml解释器早一点验证此 bean 的 id。如下:

<property name="targetName">
 <!-- id 为theTargetBean 的bean必须存在,否则抛出异常  -->
 <idref local="theTargetBean"/>
</property>

三、其他 bean的索引(依赖对象)

    在<constructor-arg/>或<property/>元素内部还可以使用ref元素.用来将bean中指定属性的值设置为对容器中的另外一个bean的引用。ref 有三个属性 bean,local,parent。其具体区别如下:


四、内部 bean

    在<constructor-arg/>或<property/>元素内部还可以使用<bean />来定义一个内部bean,如下:

<bean id="outer" class="...">
<!-- 不是使用索引来指向其他依赖对象,而是再定义一个 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 或者 name 属,也不需要指定 scope ,这些都会自动被容器忽略。内部 bean 的创建都是伴随着 outer bean的创建而创建。

五、Collections

     可以使用元素<list />,<set />,<map />和<props />来设置java collection 里的List、Set、Map和Properties的值。如下:

<bean id="moreComplexObject" class="example.ComplexObject">
 <!-- 等同于调用setAdminEmails(java.util.Properties)-->
 <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>
 <!-- 等同于调用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>

    提醒一下的是,在Map的key或 value里,Set的value,同样可以使用如下元素:

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

六、Collection 合并

    spring容器同样支持Collection的合并。我们可以在父元素里定义<list />,<map />,<set />,<props />,接着可以在子元素里定义<list />,<map />,<set />,<props />来继承或者覆盖父元素的集合。合并结果就是子元素里拥有一个合并了父、子元素里集合的Collection。如下:

<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">
      <!-- 注意在子元素里设置 merge 为 true-->
      <props merge="true">
          <prop key="sales">sales@example.com</prop>
          <prop key="support">support@example.co.uk</prop>
      </props>
  </property>
 </bean>
<beans>

    注意在子元素里设置了 “merge = true”,那上面合并结果如下:

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

    从父元素继承了<props />,同时覆盖了当中的support的元素。

    类似的,<list />,<set />,<map />也具有同样地用法。不过,在使用到<list />元素里,其内元素的顺序依然保持着,父元素的list 总所有子元素的list之前。而 Set、Map和Properties并不存在元素的顺序问题。

七、Collection 合并的限制

    在合并Collection里,不能合并类型不一致的Collection(如Set和List)。同时,如果需要抛出异常,则在子元素里的异常必须是父元素的异常的子类。需要注意的是,如果是在父元素里设置了“merge=true”,并不能得到想要的合并效果。

八、泛型集合

    在java 5 之后,可以使用泛型集合。spring的类型转换机制同样支持这种泛型集合的使用。如下:

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

   xml配置文件如下:

<beans>
  <bean id="foo" class="x.y.Foo">
      <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>

   当foo需要被注入时,Map<String,Float>的泛型信息会通过反射来获得。spring的类型转换机制可以识别出属性value的值时Float类型的,之后就将String类型的“9.99,2.75,3.99”转换为String类型。

九、null 和 空字符串值

    如下例子,会将“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) 

十、p 命名空间与简化配置

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  <!-- 添加 p 命名空间 -->
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean name="classic" class="com.example.ExampleBean">
      <property name="email" value="foo@bar.com"/>
  </bean>

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

   如上所示,演示p 命名空间的使用,深入一点来看,如果使用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
      http://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 命名空间的索引指向 bean jane。传统用法是<property name="spouse" ref="jane"/> ,而在 p 命名空间则是p:spouse-ref="jane",-ref 说明spouse不是基本的数据类型或String,而是一个索引。

十一、c 命名空间

    p 命名空间是用来简化<property />的配置,类似的有,c 命名空间可用来简化<constructor-arg />的配置。如下:

<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
      http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="bar" class="x.y.Bar"/>
  <bean id="baz" class="x.y.Baz"/>
  <-- 传统的配置方法 -->
  <bean id="foo" class="x.y.Foo">
      <constructor-arg ref="bar"/>
      <constructor-arg ref="baz"/>
      <constructor-arg value="foo@bar.com"/>
  </bean>
  <-- 使用 c 命名空间的配置-->
  <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com">
</beans>

    同时也看一下使用 c 命名空间的索引应用,同样是使用 -ref,

<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz">

十二、复合属性名

    有时候可能会看到如下的一些配置,

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

    如上的<property />其实是一个复合属性的名应用,怎么理解呢??在bean foo 理由有一个property fred,同样地在 property fred里有一个property bob,在property bob里有一个property sammy ,最后将“123”赋值给sammy。要让上述操作可以行,要求fred 和 bob 都不能为空。 

十三、使用 depends-on

    有时候在创建某个bean之前,需要先创建另外一个bean,这时就需要用到属性 depends-on。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

    使用depends-on,就要求在实例化 beanOne 之前,先实例化 manager。那又该如何先实例化多个bean呢??可以使用逗号,分号或者空格来分开,如下:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
 <property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

    如上,这就要求在实例化 beanOne之前,先实例化manager和accountDao。

十四、使用 lazy-init

    ApplicationContext实现的默认行为就是在启动时将所有singleton bean提前进行实例化(也就是依赖注入)。通常情况下这是件好事,因为这样在配置中的任何错误就会即刻被发现。有时候这种默认处理方式并不是你想要的,你希望某个bean在需要的时候才实例化,即在第一次向容器通过getBean索取bean时才实例化的。,这种方式称为“懒加载”,使用属性lazy-init可以达到这个效果。 

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>

    值得提醒一下的是,lazy-init 设置只对scop属性为singleton的bean起作用。另外,在容器层次中通过在<beans/>元素上使用'default-lazy-init'属性来控制延迟初始化也是可能的。如下面的配置:   

<beans default-lazy-init="true">
  <!-- 没有bean被提前实例化 -->
</beans>
     如果一个bean的scope属性为scope=“pototype“时,即使设置了lazy-init="false",容器启动时不实例化bean,而是调用getBean方法是实例化的。