Spring参考手册-第三章 IoC容器-3.3 依赖

 
3. 3 依赖
你的典型的企业应用不会只有单一的对象( spring 中叫做 bean )组成。即使是最简单的应用也至少会有一系列的对象组成,它们共同协作、装配成面向用户的统一应用。
下面的部分将会介绍你如何组装单独的定义的 bean 来形成一个完整的应用(应用通常都是为了实现最终用户的某个特定目标)。
3.3.1 依赖注射
依赖注射( DI )的基本原则是:对象在从构造器或者工厂方法返回时,通过构造器参数、工厂方法参数或者对象实例的属性设置来定义它们的依赖性(如那些它协同的对象)。然后,在创建 bean 的时候,容器完成依赖注射工作。这就是所谓的控制反转( Inversion of Control-IoC ): bean 自身控制初始化,或使用它自身的构造器来定位自身的依赖,这有点类似于 Service Locator 模式。
什么明显,当 DI 原则被充分应用的时候,代码将会变得更干净( cleaner ),同时应用实现了高层次的分离也会变得更容易,此时 bean 并不能感知它的依赖,但这些依赖却能够被提供给它们( bean 甚至并不知道依赖的对象在哪儿,或者依赖的对象是什么类)。
正如前面所述, DI 有两种主要的实现方式,分别叫做: Setter Injection Constructor Injection
3.3.1 .1 Setter Injection (注射)
在调用一个无参数的构造器或者静态工厂方法创建 bean 之后,通过调用 bean setter 方法来初始化 bean 的方法就叫做 setter injection
下面的例子中,该类只能用 setter 方法实现依赖注射。注意,该类只是一个平常的 Java 对象。
public class SimpleMovieLister {
 
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
 
    // a setter method so that the Spring container can 'inject' a MovieFinder
    public void setMoveFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    
    // business logic that actually 'uses' the injected MovieFinder is omitted...
}
3.3.1 .2 Constructor Injection (构造器注射)
构造器注射方式是通过调用一个采用参数(参数表示协作或者依赖的事物)的构造器来实现的。此外,通过调用带有特定参数的静态工厂方法来创建 bean 的方式也可以看作是这种方法的延伸,下面的章节也把这两种情况类似看待。
下面的例子中,该类只能用构造器注射方法实现依赖注射。再次注意,该类也是一个普通类。
public class SimpleMovieLister {
 
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
 
    // a constructor so that the Spring container can 'inject' a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    
    // business logic that actually 'uses' the injected MovieFinder is omitted...
}
 

选择constructor- 还是setter
Spring团队推荐使用setter方法,因为如果constructor的参数很多的话,就会变得很难处理,特别是当一些参数是可选的时候。Setter方法的出现使你能够在bean生成以后可以重新配置(重新注射)依赖。(JMX MBeans的管理就是一个典型的例子)。
经过如此,constructor方法还是会被一些纯化论者所推荐。他们的理由是:在bean初始化的时候就将bean的所有依赖注射完成意味着该对象可以尽量少的在客户端调用代码中出现。问题是:不能重新配置(注射)这个bean。
这儿并不存在硬性规定。无论哪种DI方式要依赖于具体的类;有时候,当你使用第三方的类的时候,你并没有类的具体实现细节,也并未开放任何的setter方法,那么此时的选择已经确定,那就是constructor方式。

BeanFactory 支持上面两种方式的注射方法。(实际上,如果部分依赖已经通过构造器方法注射后,它然后支持通过 setter 方法注射)依赖的配置在容器中表现为带有 PropertyEditor 实例的 BeanDefinition ,它知道如何变换属性的格式。然而, Spring 的大多数用户一般不会直接处理这些类(例如通过程序方式),他们宁愿去处理将被转换成类实例的 XML 定义文件,然后加载整个 Spring IoC 容器。
Bean 依赖的处理方式通常有以下几种:
l         BeanFactory 通常在初始化的时候,会读取所有 bean 的配置文件。(大多数 Spring 用户使用 BeanFactory 或者 ApplicationContext
l         Bean 使用属性、构造器参数或者静态工厂方法参数来表示依赖。在创建的时候,这些依赖会传递给 bean
l         每个属性或者构造器参数要么是要设置的值,要么是对于容器中另外的 bean 的引用。
l         每个属性或者构造器参数能够被转换成实际要求的类型。默认的, Spring 可以将 String 类型的值转换成内建类型,如 int long String boolean 等。
认识到 Spring 实际上是在容器创建的时候来验证每个 Bean 的配置信息这点是十分重要的,包括 Bean 属性引用是引用合法的 bean 的验证。(如,被引用的 bean 要求是定义在同一个容器中。然而, Bean 属性的设置是直到 bean 创建完成的时候才完成的。对于那些单例模式且被设置成预初始化的 bean ,是在容器初始化的时候就创建的,而其他的 bean 实际上是在被请求的时候才创建的。当 bean 被创建的时候,这会引起具有依赖的 bean 被创建,包括依赖的、依赖的依赖的等也被一起创建)。

循环依赖:
如果使用典型的构造器注射方式,有可能会形成循环依赖的情况。
考虑如下情况:类A在构造器注射时,需要类B实例,而类B同样需要A的实例。如果你配置A和B互相注射,那么Spring IoC容器会在运行时检测到循环引用,然后抛出BeanCurrentlyInCreationException异常。
解决此问题的一个方法是用setter方法代替构造器方法。另外一个解决方法,是不要使用构造器方法,而是使用setter。

你一般情况下可以相信 Spring 会做正确的事情。 Spring 会在容器加载的时候,监测不匹配的配置项,例如对于不存在 bean 的引用或者循环依赖。容器将尽可能晚的设置属性和解析依赖(如仅在请求发生的时候才会去创建依赖)。这意味着 Spring 在你请求一个 bean (如果该 bean 创建错误或者创建依赖的时候错误)的时候,会比较晚的抛出异常。当一个 bean 在发现非法属性的时候就会抛出异常,这也引起容器抛出异常。某些配置项的晚邦定正是 ApplicationContext 接口默认预实例化单例 bean 的原因。 ApplicationContext 创建的同时,相关的配置项也一起创建,而这是以创建那些暂时不需要的 bean 所耗费的时间和内存为代价的。当然,你可以去重载这种默认行为,如将单例 bean 设置成“懒初始化( lazy-initialize )”(如:并不预初始化)。
最后,要说一下的是,在一个或者多个 bean 被注射到一个依赖的 bean 的时候,每个被注射的 bean 在注射之前就是配置完成的(通过 DI 方式)。也就是说,如果 bean A 对于 bean B 依赖,那么 Spring IoC 容器在调用 A Setter 方法注射 B 之前,就完整配置好 B ;‘完整配置( totally configure )’的意思是 bean 被初始化(如果不是预实例化的单例 bean 的话), bean 的所有依赖被设置并且相关的生命周期方法会被调用。(如配置初始化方法 -configured init method 或者初始化 bean 回调方法 -initializingbean callback method )。
3.3.1 .3 例子
首先,第一个例子采用 XML 配置方式的 DI 。参看下面的部分的 bean 定义文件。
<bean id="exampleBean" class="examples.ExampleBean">
 <!-- setter injection using the nested <ref/> element -->
 <property name="beanOne"><ref bean="anotherExampleBean"/></property>
 <!-- setter injection using the neater 'ref' attribute -->
 <property name="beanTwo" ref="yetAnotherBean"/>
 <property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
 
public class ExampleBean {
 
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
   private int i;
 
    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }
 
    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }
 
    public void setIntegerProperty(int i) {
        this.i = i;
    }    
}
可以看出,针对 XML 配置文件中的属性,已经声明了相应的 setters 方法。
下面,另外一个例子是使用构造器注射方式。参看下面的配置片断和相应的 Java 类代码。
<bean id="exampleBean" class="examples.ExampleBean">
 
 <!-- constructor injection using the nested <ref/> element -->
 <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
 
  <!-- constructor injection using the neater 'ref' attribute -->
 <constructor-arg ref="yetAnotherBean"/>
 
  <constructor-arg type="int" value="1"/>
</bean>
 
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
 
 
public class ExampleBean {
 
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
可以看出, bean 定义中的构造器参数指定了将会传递给 ExampleBean 构造器的参数值。
下面,考虑构造器方法的一个变形的情况, Spring 通过调用静态工厂方法来返回对象实例。
<bean id="exampleBean" class="examples.ExampleBean"
      factory-method="createInstance">
 <constructor-arg ref="anotherExampleBean"/>
 <constructor-arg ref="yetAnotherBean"/>
 <constructor-arg value="1"/> 
</bean>
 
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
 
public class ExampleBean {
 
    // a private constructor
    private ExampleBean(...) {
      ...
    }
    
    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
            AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        ExampleBean eb = new ExampleBean (...);
        // some other operations
        ...
        return eb;
    }
}
注意,传递给静态工厂方法的参数是用 constructor-arg 元素实现的,这点和构造器方法是一样的。另外重要的一点是,通过工厂方法返回的类并不一定就是包含了工厂方法的类,尽管本例子是这样。实例工厂方法和这类似(不同的是用 factory-bean 属性代替 class 属性),细节这里不再赘述。
3.3.2 构造器参数解析
当使用参数类型时,构造器参数解析匹配将会发生。如果在 bean 定义中,不存在参数歧义的情况,那么 bean 定义文件的构造器参数顺序就是实际传递给 bean 构造器的参数顺序。参看下面的类:
package x.y;
 
public class Foo {
 
    public Foo(Bar bar, Baz baz) {
        // ...
    }
}
此时,不存在潜在的歧义情况(假定类 Bar 和类 Baz 无继承层次关联)。这样,下面的配置将会正常工作,并且也不需要指定构造器参数的索引,也不需要显式指定参数类型,它会按照你期望的那样正常工作。
<beans>
    <bean name="foo" class="x.y.Foo">
        <constructor-arg>
           <bean class="x.y.Bar"/>
        </constructor-arg>
        <constructor-arg>
            <bean class="x.y.Baz"/>
        </constructor-arg>
    </bean>
</beans>
bean 被引用的时候,可以知道类型,并且发生匹配(正如前面的例子那样)。然而,在使用简单值类型,如 <value>true<value> 的时候, Spring 无法确定值的类型,此时如果没有其他配置的话,将无法正确匹配。参考下面的类,后面将会使用。
package examples;
 
public class ExampleBean {
 
    // No. of years to the calculate the Ultimate Answer
    private int years;
    
    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;
 
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
 
3.3.2 .1 构造器参数类型匹配
对于上面的情况可以通过使用 'type' 属性指定构造器参数类型,从而实现简单类型匹配。如:
<bean id="exampleBean" class="examples.ExampleBean">
 <constructor-arg type="int" value="7500000"/>
 <constructor-arg type="java.lang.String" value="42"/>
</bean>
3.3.2 .2 构造器参数索引
可以通过使用 index 属性来显式的制定构造器参数的索引。例如:
 
<bean id="exampleBean" class="examples.ExampleBean">
 <constructor-arg index="0" value="7500000"/>
 <constructor-arg index="1" value="42"/>
</bean>
在一个构造器有多个同类型参数的情况下,通过指定参数索引,同样可以解决多个简单值类型参数的歧义问题。注意,索引从 0 开始。
提示:
指定构造器参数索引是 IoC 容器的首选方式。
3.3.3 Bean 属性和构造器参数细节
正如前面所述, bean 属性和构造器参数可以引用其他受管理的 bean (协作者),或者是内部定义的值。 Spring XML 配置中的 <property/> <constructor-arg/> 属性有很多子元素就可以实现这点。
3.3.3 .1 直接赋值(原始类型,串型等)
<value/> 属性以易于阅读的方式来制定属性或者构造器参数的值。正如前面所述, JavaBean PropertyEditors 将这些值从 java.lang.String 类型转换成相应的类型。
<bean id="myDataSource" destroy-method="close"
    class="org.apache.commons.dbcp.BasicDataSource">
 <!-- results in a setDriverClassName(String) call -->
 <property name="driverClassName">
    <value>com.mysql.jdbc.Driver</value>
 </property>
 <property name="url">
    <value>jdbc:mysql://localhost:3306/mydb</value>
 </property>
 <property name="username">
    <value>root</value>
 </property>
</bean>
3.3.3 .1.1 idref 元素
Idref 元素是一种简单的传递容器中的其他的 bean id 的“错误验证”( error-proof )方式。( <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</value>
    </property>
</bean>
第一种方式比第二种更好,原因是idref标志让容器在分发时验证被引用的bean是否实际存在。第二种情况中,并没用验证 'targetName' 属性引用的 bean 是否存在。
对于第二种方式,在被引用 bean 被实例化之前,例外将会随时可能抛出(往往会导致系统崩溃)。如果被引用的是原型 bean ,那么,那么异常可能会在容器分发完毕之后很长时间才会抛出。
此外,如果被引用的 bean 是在同样的 XML 配置单元,且 bean name 就是 bean id ,可以使用 'local' 属性来指定,这样,在 XML 文档解析的时候,就可以验证 bean 的合法性。
<property name="targetName">
   <!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown -->
   <idref local="theTargetBean"/>
</property>
3.3.3 .2 对于其他 bean 的引用(协作者)
Ref 是最后一个可以包含在 <constructor-arg/> 或者 <property/> 中的元素。它被用来指定引用其它的 bean 的属性值(协助者)。正如前面所述,被引用的 bean 被认为是引用它的 bean 的依赖,会在属性设置之前被实例化(对于单例 bean 可能已经完成了初始化)。
所有的引用最终只是对于另外一个对象的引用,但是有三种方法来指定被引用对象的 id 或者 name ,不同的方法决定对象的范围和验证方式的不同。
通过使用 <ref/> 标签的 bean 属性来制定目标 bean 是最常用的方式,这将会创建一个对于同容器或者父容器的 bean 的引用(无论在不在同样的 XML 文件中)。 'bean' 属性的值可以是目标 bean 'id' 属性,或者是 'name' 属性。
<ref bean="someBean"/>
通过 local 属性来制定目标 bean 的方式,可以利用 XML 解析器验证同一文件中的 XML ID 引用的功能。 Local 属性必须和目标 bean id 属性值相同。如果没有匹配到对应的元素,那么 XML 解析器将会抛出异常。因此,如果目标 bean 在同一个定义文件中,那么采用 local 属性是最好的方式(可以尽可能早的发现错误)。
<ref local="someBean"/>
通过 'parent' 属性来制定目标 bean ,可以引用当前容器的父容器中的 bean 'parent' 的值可以是目标 bean id 属性,或者 'name' 属性值,且目标 bean 必须在当前容器的父容器中。当存在容器继承情况,而且你需要封装一个父容器中的 bean ,且需要实现和父 bean 的同名代理时(例如,子 bean 的定义将会重载父 bean 的定义)。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of 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 as here -->
</bean>
(坦率的说, 'parent' 属性较少使用。
3.3.3 .3 内部 bean
<property/> 或者 <constructor-arg> 属性的 <bean/> 元素用来定义叫做内部 bean bean inner bean )。内部 bean 的定义不需要 id 或者 name ,由于 id name 将会被容器忽略,所以不指定任何 id 或者 name 就是最好的方式。
参看下面的一个内部 bean 的例子:
<bean id="outer" class="...">
 <!-- instead of using a reference to a target bean, simply define the target inline -->
 <property name="target">
    <bean class="com.mycompany.Person"> <!-- this is the inner bean -->
      <property name="name" value="Fiona Apple"/>
      <property name="age" value="25"/>
    </bean>
  </property>
</bean>
注意,在内部 bean 的情况下, 'singleton' 'id' 或者 'name' 属性将会被容器完全忽略。内部 bean 总是以匿名方式存在,且它们总是原型 bean 。请记住,试图将内部 bean 注射到依赖的 bean 中是不可能的。
3.3.3 .4 集合
<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@somecompany.org</prop>
        <prop key="support">support@somecompany.org</prop>
        <prop key="development">development@somecompany.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>
                <value>yup an entry</value>
            </key>
            <value>just some string</value>
        </entry>
        <entry>
            <key>
                <value>yup a ref</value>
            </key>
            <ref bean="myDataSource" />
        </entry>
    </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 key 或者 value 的值,可以使下面的元素中的一种:
bean | ref | idref | list | set | map | props | value | null
3.3.3 .4.1 集合合并
Spring2.0 开始,容器也支持集合的合并。应用开发者可以定义一个父类型的 <list/> , <map/> , <set/> 或者 <props/> 元素,然后定义子类型的 <list/> , <map/> , <set/> 或者 <props/> 元素,以实现对于父集合值的继承和重载;子集合的值是父、子集合的值合并的结果,子集合元素重载对应的父集合的值。
请注意,合并的定义使用的是父子 bean 机制( parent-child )。该概念还仍然没介绍,所以不熟悉的读者在继续之前,需要阅读一下相关的章节。(参看第 3.6 章,“ bean 定义继承”)。
下面的例子能够很好的演示这个特性:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@somecompany.com</prop>
            <prop key="support">support@somecompany.com</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">sales@somecompany.com</prop>
            <prop key="support">support@somecompany.co.uk</prop>
        </props>
    </property>
</bean>
<beans>
请注意 child bean 的定义中, adminEmails 属性的 <props/> 元素中, merge 属性设置为 true 。当 child bean 实际在容器中实例化的后,实例将会拥有父、子 bean 合并后的 adminEmails 属性集合。
administrator=administrator@somecompany.com
sales=sales@somecompany.com
support=support@somecompany.co.uk
注意,子属性集合的值继承了所有父属性集合的元素值,同时,子属性集合中的 support 值重载了父集合中的值。
合并行为可以类似的应用与 <list/> , <map/> , <set/> 集合类型。对于 <list/> 元素,与 List 集合类型(例如一个有序集合值)关联的语义会被维护,父值始终前导子值。对于 Map Set Properties 集合类型,并不是有序的,因此,对于 Map Set Properties 集合的实现类型,容器并不维护有序的语义。
最后,再说明几个关于合并的问题;不可以合并不同类型集合(如不能合并 Map List ),因此,如果你尝试那么做,那么一个异常将被抛出;需要明确的一点是, 'merge' 属性必须在“低层次”( lower level )设置,并且是继承型且子定义型的;如果在父集合定义中指定 'merge' 属性,那么并不会产生期望的合并;最后,请注意合并特性只有在 Spring2.0 中可以使用。(或者以后的版本)
3.3.3 .4.2 强类型集合(仅对于 Java5+
如果你有幸成为 Java5 Tiger )用户之一,你将会了解到它是支持强类型集合的(我推荐的)。也就是说,可以声明一个只包含 String 元素的 Collection 类型。
如果你利用 Spring 将强类型的 Collection 注射到 bean ,那么你可以利用 Spring 的类型转化( type-conversion )支持,它在元素被添加到 Collection 之前,将强类型 Collection 实例的元素转化为合适的类型。
下面的例子可以清晰地说明这点;参看下面的类定义和它的 XML 配置内容 ...
public class Foo {
                
    private Map<String, Float> accounts;
    
    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
 
}
<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'bean 'accounts' 属性的时候,可以通过反射机制来获取强类型集合 Map<String,Float> 的元素类型信息,所以 Spring 的类型转换机制将会把值看作是 Float 类型,因此 '9.99', '2.75' , '3.99' 将被转换成实际的 Float 类型。
3.3.3 .5 空类型
<null/> 元素被用来处理空值。 Spring 把赋予属性等的空参数视为空串。下面的 XML 配置段演示了如何将 email 属性置为空的串值( ”” )。
<bean class="ExampleBean">
 <property name="email"><value></value></property>
</bean>
上面的配置等同于下面的 Java 代码: exampleBean.setEmail(“”) 。指定的 <null> 元素也可以被用来指定空值。例如:
<bean class="ExampleBean">
 <property name="email"><null/></property>
</bean>
上面的配置等同于下面的 Java 代码: exampleBean.setEmail(null)
3.3.3 .6 XML 配置方式的捷径
需要配置一个值或者 bean 引用的情况很多见, Spring 中有一些比使用 <value/> 或者 <ref/> 元素更便捷的方式。 <property/> , <constructor-arg/> , <entry/> 元素均支持 ’value’ 属性,这可以用来替代内嵌的 <value/> 元素。参看下面的例子:
<property name="myProperty">
 <value>hello</value>
</property>
<constructor-arg>
 <value>hello</value>
</constructor-arg>
<entry key="myKey">
 <value>hello</value>
</entry>
等同于:
<property name="myProperty" value="hello"/>
<constructor-arg value="hello"/>
<entry key="myKey" value="hello"/>
一般来说,在手工写配置的时候,建议你使用便捷的方式( Spring 团队也是这么做的)。 <property/> <constructor-arg/> 元素支持 'ref' 属性,可以用来代替内嵌的 <ref/> 元素。参看下面的例子:
<property name="myProperty">
 <ref bean="myBean">
</property>
<constructor-arg>
 <ref bean="myBean">
</constructor-arg>
等同于:
<property name="myProperty" ref="myBean"/>
<constructor-arg ref="myBean"/>
注意,这种方式是等同于 <ref bean=”xxx”> 配置的;对于 <ref local=”xxx”> 并没有类似的便捷方式。为了实现强制的本地引用,你必须使用原来的配置方式。
最后, entry 元素也支持制定 key 值或者 map 值得便捷方式,它使用 ’key’ ’key-ref’ ’value’ ’value-ref’ 属性来实现。参看下面的例子:
<entry>
 <key>
    <ref bean="myKeyBean" />
 </key>
 <ref bean="myValueBean" />
</entry>
等同于:
<entry key-ref="myKeyBean" value-ref="myValueBean"/>
同样,这种方式是等同于 <ref bean=”xxx”> 配置的;对于 <ref local=”xxx”> 并没有类似的便捷方式。
3.3.3 .7 复合属性的命名
在设置 bean 属性的时候,复合或者内嵌的属性命名是合法的,只要所有在路径上除了最后属性都是非空。例如:
<bean id="foo" class="foo.Bar">
 <property name="fred.bob.sammy" value="123" />
</bean>
Foo 这个 bean 有一个叫做 fred 的属性,而 fred 则有一个叫做 bob 的属性,同时 bob 则有一个叫做 sammy 的属性,最后的这个属性 sammy 被设置为 123 Foo fred 属性, fred bob 属性必须在 bean 被构造后保持非空,否则 NullPointerException 异常将被抛出。
3.3.4 使用 depends-on
大多数情况下, bean 的依赖可以简单的通过设置属性来实现,通常利用 XML 配置的 <ref/> 元素来实现。另外一种方式是, bean 会被赋予依赖的对象 ID (使用串值或者 <idref/> 元素)。第一方式, bean 以程序方式向容器请求它的依赖对象。而第二种,在依赖产生之前,被依赖的对象实际上已经被实例化。
有些情况下, bean 之间的依赖相对不是很直接(例如,数据库驱动注册,此时,一个静态的类初始化方法需要被触发),那么可以使用 'depends-on' 属性来在 bean 被使用之前,显式的触发它的实例化。参看下面的例子:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
 
<bean id="manager" class="ManagerBean" />
如果需要表示多个 bean 的依赖,可以用逗号,空格或者分号等所有合法的分隔符将 'depends-on' 属性的值隔开,参看下面的例子,它演示了如何配置对于多个 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" />
3.3.5 懒实例化 bean
ApplicationContext 的默认行为是在启动的时候,尽可能早的预实例化所有的单例 bean 。所谓“预实例化”是指 ApplicationContext 实例会在自身的初始化过程中,尽可能的创建和配置所有的单例 bean 。通常这是正确的,因为这意味着在配置中,或者在相关辅助环境中的错误会被立刻发现(相比可能会在数小时甚至数天才被发现而言)。
然后,有时这种默认的行为却不是我们所需要的。当你不需要 ApplicationContext 预实例化单例 bean 时,你可以(在 bean 定义文件的基础上)有选择地控制这种默认行为,将 bean 设置为懒实例化( lazy-initialized )。懒实例化 bean 告诉容器实在启动时创建,还是当被请求的时候才创建。
通过 XML 配置 bean 时,是通过 'lazy-init' 属性来控制“懒加载”( lazy-loading )的。参看下面的例子:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true">
    <!-- various properties here... -->
</bean>
 
<bean name="not.lazy" class="com.foo.AnotherBean">
    <!-- various properties here... -->
</bean>
对于上面的配置,叫做 ’lazy’ bean ApplicationContext 初始化时不会被预实例化,而叫做 ’not.lazy’ bean 则会被尽早的预实例化。
关于懒实例化,需要说明的一点是,即使 bean 被配置成懒实例化,假如该 bean 是一个非懒实例化的 bean 的依赖,那么在 ApplicationContext 预实例化 bean 的时候,会创建它的所有依赖,即使它们中存在懒实例化的 bean !所以,当容器把你配置成懒实例化的 bean 实例化的时候,不必疑惑;那是因为,这个懒实例化的 bean 是被依赖注射到了你的配置中其他某个地方的非懒实例化的单例 bean
在容器层次,还可以通过使用 <beans/> 元素的 'default-lazy-init' 属性实现懒实例化;参看下面的例子:
<beans default-lazy-init="true">
    <!-- no beans will be eagerly pre-instantiated... -->
</beans>
3.3.6 自装配的协作者
Spring IoC 容器可以自装配 Bean 之间的协作关系。这意味着,可以让 Spring 通过检查 BeanFactory 的内容,来解析 bean 的协作关系。自装配有 5 种模式。自装配是针对 bean 定义的,因此,可以指定一些 bean 自装配,而另外一些则不需要。使用自装配模式,完全可能不需要指定属性或者构造器参数,这样可以少一些手工配置。在 XML 配置中,自装配模式通过使用 <bean/> autowire 属性来实现。参看, autowire 允许的值列表:
 
 
 
 
 
 
 3.2.  自装配模式
模式
解释
no
不使用自装配模式。 Bean 的引用使用 ref 元素定义。这是默认方式,对于大多数开发者来说,建议不要修改这个默认配置,因为,显式的指定协作者的方式有更多的可控性和明确性。从某种意义上说,这是系统构造的标准方式。
byName
属性名方式自装配。容器会寻找和属性同名的 bean 来进行装配。例如,假设有一个 bean 被设置成这种方式,它包含一个 master 属性(也就是说,它有一个说它 Master(...) 方法), Spring 会查询命名为 master bean ,然后用它来设置属性。
byType
属性类型方式自装配。这种方式假定在容器中存在一个和属性类型相同的 bean 。如果在容器中有两个这样的 bean 存在,那么将会抛出致命异常,这种情况下是不允许使用这种方式的。如果,没有这样的 bean ,那么没有任何问题;该属性不被设置。如果需要有提示,那么将 dependency-check 属性设置为 ”objects” ,那么这种情况下,将会抛出一个错误。
constructor
类似于 byType 方式,但是针对的是构造器参数。如果在容器中,不存在与构造器参数类型相同的 bean ,致命异常被抛出。
autodetect
通过 bean 类自己来选择 constructor 还是 byType 方式。如果 bean 采用的是默认的构造器,那么将会使用 byType 方式。
注意,对于 property constructor-arg 的显式依赖设置会覆盖自装配模式。也要注意一点的是,自装配模式不适用与简单类型属性,如原始类型、 Strings Classes 类型(包括这些简单类型组成的数组类型)。(尽管这是应该被设计和考虑实现的一个特性)自装配模式应该和依赖检查结合起来,这样,在装配完成后,可以进行进一步的检查。
了解自装配模式的优劣也是很重要的。
下面是优点:
l         自装配模式可以大大减少手工配置内容。然而,在这方面,诸如 bean 模版等机制也可以做到(在后面讨论)。
l         自装配模式在对象发生更新的时候,可以自动的更新配置情况。例如,当需要为一个类增加额外的依赖时,那么通过自装配可以在不修改配置信息的情况下实现。因此,在开发期间,自装配模式什么有用,当代码基线逐步稳定以下,可以在转换成显式配置方式。
下面是缺点:
l         自装配模式相比而言更加具有不可确定性。尽管,在上面的表格中提到, Spring 尽可能的避免因为猜测而导致不可预知的结果,但是 Spring 管理的那些对象之间的关系变得不再明确。
l         对于那些需要从 Spring 容器获取信息,然后产生描述信息的工具来说,装配过程信息不可利用;
l         当容器中只存在唯一一个与 setter 方法或者构造器参数类型匹配的 bean 定义的时候,自装配模式才能正常工作。如果存在任何潜在的不确定性,那么你最好还是进行显式装配。
具体使用哪种方式都没有“对”或者“错”。但在一个项目中,保持一致性还是最好的方式;例如,如果在项目中基本没有使用自装配模式,当你只是在一两个 bean 定义中采用这种方式的时候,很可能让人产生迷惑。
3.3.6 .1 禁止 Bean 的自装配
你也可以禁止 bean 的自装配模式。当使用 Spring XML 配置 bean 时, <bean/>
'autowire-candidate' 属性可以设置成 ’false’ ;这样,容器就会将该 bean 从自装配体系中排除出去。
当你需要指定某个 bean 禁止以自装配方式注射到其他 bean 的时候,这个属性就变得
很有用。当然,这并不是说,被排除的 bean 自身不能使用自装配方式 ... ,而是说它自身不可以作为自装配的候选对象。
3.3.7 依赖检查
 Spring IoC 容器可以检查当前容器中的 bean 的未解析依赖的存在性。检查的内容包括那些没有在 bean 定义中设置值的 bean 属性,或者那些应该通过自装配模式设置的属性。
  当你需要确认 bean 的所有属性(或某种类型的所有属性)是否被设置时,该特性就显得很有用了。当然,大多数情况下, bean 会有属性的默认值,或者有些属性在有些场景下不需要赋值,因此,该特性最好有节制的使用。依赖检查同样可以对于单独的 bean 进行配置。依赖检查包括几种不同的模式。使用 XML 配置时,通过指定 'dependency-check' 属性来实现。该属性可以取下面的值。
 
 
 3.3.  依赖检查模式
模式
解释
none
不执行依赖检查。即使 bean 属性没有赋值也没有关系。
simple
对于原始类型和集合类型属性的依赖检查。(不检查协作者类型依赖,如 bean 的引用)
object
对于协作者的依赖检查。
all
对于协作者、原始类型和集合类型的依赖检查。
如果是 Java5 Tiger )用户,可以参考源码级的注释,参考第 25.3.1 章,“ @Required ”。
3.3.8 方法注射
  在大多数应用场合,容器中的 bean 主要以单例方式存在。如果某个单例 bean 需要和另外一个单例 bean 合作,或者某个非单例 bean 需要和另外一个非单例 bean 合作,通常的处理方式是将一个定义为另外一个 bean 的属性。然后,对于 bean 生命周期不同的时,存在一个问题。考虑下面这种情况:单例 bean A 需要使用非单例 bean B (原型),可能 A 的每个方法均需要调用 B 。容器仅创建 A 的一个实例,因此只可以设置 A 的属性一次。不可能每次当 B 被请求的时候,都用新的 B 实例来设置。
  对于这种情况的一个处理方式是一定程度上放弃控制反转机制。 A 可以实现 BeanFactoryAware 接口,这样可以被容器感知,然后使用程序方式让容器在需要的时候重新请求 B 的实例,如调用 getBean(“B”) 方法。参看下面的例子:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
 
// lots of Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
 
public class CommandManager implements BeanFactoryAware {
 
   private BeanFactory beanFactory;
 
   public Object process(Map commandState) {
      // grab a new instance of the appropriate Command
      Command command = createCommand();
      // set the state on the (hopefully brand new) Command instance
      command.setState(commandState);
      return command.execute();
   }
 
   // the Command returned here could be an implementation that executes asynchronously, or whatever
   protected Command createCommand() {
      return (Command) this.beanFactory.getBean("command"); // notice the Spring API dependency
   }
 
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      this.beanFactory = beanFactory;
   }
}
上面的例子不是一个合适的解决方法,因为,业务代码和 Spring 框架出现耦合。方法注射 -Spring IoC 容器的高级特性,可以用清晰的方式处理这种情况。
3.3.8 .1 “查找方式”( lookup )的方法注射

方法注射。。。:
…,有点类似于Tapestry 4.0的情况,开发者写好抽象的属性,然后Tapestry将会在运行时重载并实现它。
可以参考下面的关于方法注射介绍的 blog

  查找方式的方法注射基于容器对于它管理的 bean 的方法重载能力实现,这样,容器可以查找需要的 bean ,然后返回。上面提到的场合中,需要查找的 bean 是原型的(当然,也可以查找单例,但对于单例可以直接注射实例)。 Spring 框架使用基于 CGLIB 库的字节码创建方式,动态的创建重载的方法子集,并以此实现方法注射。
  看了前面的代码片断( CommandManager 类), Spring 容器会动态实现对于 createCommand() 方法的重载。这样, CommandManager 类不会产生对于 Spring 框架的依赖,可以参看下面改写后的例子:
package fiona.apple;
 
// no more Spring imports! 
 
public class CommandManager {
 
   public Object process(Object command) {
      // grab a new instance of the appropriate Command interface
      Command command = createCommand();
      // set the state on the (hopefully brand new) Command instance
      command.setState(commandState);
      return command.execute();
   }
 
    // mmm, but where is the implementation of this method?
   protected abstract CommandHelper createHelper();
 
}
  该类( CommandManager )包含了要被注射的方法,该方法必须符合下面的形式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
  如果方法是抽象的,动态创建的子类将会实现它。如果不是,那么将会重载它。参看下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
 <!-- inject dependencies here as required -->
</bean>
 
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
 <lookup-method name="createCommand" bean="command"/>
</bean>
 Bean commandManager 会在每次需要 bean command 的时候调用它自己的 createCommand() 方法。
注意,构造器和 setter 注射都可以使用查找方法注射的方式。
要让动态生成的子类正常运行,那么必须把 CGLIB 这个 jar 放在类路径上( classpath )。此外,将要被继承的类不可以是 final 的,并且需要被重载的方法也不可以是 final 的。而且,测试类的抽象方法也有其必要性,因此,你可以自己实现该类的子类,并提供抽象方法的一个实现。最后,要进行方法注射的 bean 不可以被序列化。
提示:
感兴趣的读者可能发现 ServiceLocatorFactoryBean (位于 org.springframework.beans.factory.config 包)可以利用 ... 使用方法类似于 ObjectFactoryCreatingFactoryBean ,但允许你使用你自己的 lookup 接口,而不是必须使用 Spring 指定的接口,如 ObjectFactory 。可以参考 java 文档(很多)进一步了解 ServiceLocatorFactoryBean 的使用方法(这种方式有利于进一步减少对于 Spring 框架的依赖)。
3.3.8 .2 任意的方法替换
  相比 lookup 方法注射方式而言,还有一种不太用的方法注射方法,可以用方法替代另外其他方法。用户可以忽略本节的剩余部分(描述了 Spring 的高级特性),等到使用的时候再看。
  当使用 XML 配置方式时, replaced-method 元素用来指定方法的另外一个替代方法。参看下面的类,包括了将要被替换的方法 computeValue
public class MyValueCalculator {
 
 public String computeValue(String input) {
    // some real code...
 }
 
 // some other methods...
 
}
  另外一个实现了 org.springframework.beans.factory.support.MethodReplacer 接口的类提供了另外一个方法。
/** meant to be used to override the existing computeValue
    implementation in MyValueCalculator */
public class ReplacementComputeValue implements MethodReplacer {
 
    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ... 
        return ...;
}
  下面的 bean 定义将完成方法替代:
<bean id="myValueCalculator class="x.y.z.MyValueCalculator">
 <!-- arbitrary method replacement -->
 <replaced-method name="computeValue" replacer="replacementComputeValue">
    <arg-type>String</arg-type>
 </replaced-method>
</bean>
 
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
  可以使用 <replaced-method> 元素中的一个或者多个 <arg-type> 元素来说明将要被替代的方法的参数特点。注意,对于参数的说明只在有方法需要被替代,而且类中存在多个变量的时候才需要。为了方便,参数类型的描述可以采用简写方式。例如,下面的都是匹配 java.lang.String 类型的。
    java.lang.String
    String
   Str
  由于参数的数量大多数情况下足以区分每个可能的选择,这种方式可以节省许多的手工输入工作,你只需要手工输入足以表达参数类型的最短的字符串就可以了。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值