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

来源:互联网 发布:c语言if语句 编辑:程序博客网 时间:2024/05/29 11:08
 
3. 3 依赖
你的典型的企业应用不会只有单一的对象(spring中叫做bean)组成。即使是最简单的应用也至少会有一系列的对象组成,它们共同协作、装配成面向用户的统一应用。
下面的部分将会介绍你如何组装单独的定义的bean来形成一个完整的应用(应用通常都是为了实现最终用户的某个特定目标)。
3.3.1依赖注射
依赖注射(DI)的基本原则是:对象在从构造器或者工厂方法返回时,通过构造器参数、工厂方法参数或者对象实例的属性设置来定义它们的依赖性(如那些它协同的对象)。然后,在创建bean的时候,容器完成依赖注射工作。这就是所谓的控制反转(Inversion of Control-IoC):bean自身控制初始化,或使用它自身的构造器来定位自身的依赖,这有点类似于Service Locator模式。
什么明显,当DI原则被充分应用的时候,代码将会变得更干净(cleaner),同时应用实现了高层次的分离也会变得更容易,此时bean并不能感知它的依赖,但这些依赖却能够被提供给它们(bean甚至并不知道依赖的对象在哪儿,或者依赖的对象是什么类)。
正如前面所述,DI有两种主要的实现方式,分别叫做:Setter InjectionConstructor Injection
3.3.1.1 Setter Injection(注射)
在调用一个无参数的构造器或者静态工厂方法创建bean之后,通过调用beansetter方法来初始化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类型的值转换成内建类型,如intlongStringboolean等。
认识到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容器在调用ASetter方法注射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(协作者),或者是内部定义的值。SpringXML配置中的<property/><constructor-arg/>属性有很多子元素就可以实现这点。
3.3.3.1 直接赋值(原始类型,串型等)
<value/>属性以易于阅读的方式来制定属性或者构造器参数的值。正如前面所述,JavaBeanPropertyEditors将这些值从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元素是一种简单的传递容器中的其他的beanid的“错误验证”(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配置单元,且beanname就是beanid,可以使用'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解析器验证同一文件中的XMLID引用的功能。Local属性必须和目标beanid属性值相同。如果没有匹配到对应的元素,那么XML解析器将会抛出异常。因此,如果目标bean在同一个定义文件中,那么采用local属性是最好的方式(可以尽可能早的发现错误)。
<ref local="someBean"/>
通过'parent'属性来制定目标bean,可以引用当前容器的父容器中的bean'parent'的值可以是目标beanid属性,或者'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/>元素用来定义叫做内部beanbeaninner bean)。内部bean的定义不需要id或者name,由于idname将会被容器忽略,所以不指定任何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集合类型的属性和构造器参数,如:ListSetMapProperties
<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>
注意,mapkey或者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集合类型(例如一个有序集合值)关联的语义会被维护,父值始终前导子值。对于MapSetProperties集合类型,并不是有序的,因此,对于MapSetProperties集合的实现类型,容器并不维护有序的语义。
最后,再说明几个关于合并的问题;不可以合并不同类型集合(如不能合并MapList),因此,如果你尝试那么做,那么一个异常将被抛出;需要明确的一点是,'merge'属性必须在“低层次”(lower level)设置,并且是继承型且子定义型的;如果在父集合定义中指定'merge'属性,那么并不会产生期望的合并;最后,请注意合并特性只有在Spring2.0中可以使用。(或者以后的版本)
3.3.3.4.2 强类型集合(仅对于Java5+
如果你有幸成为Java5Tiger)用户之一,你将会了解到它是支持强类型集合的(我推荐的)。也就是说,可以声明一个只包含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被设置为123Foofred属性,fredbob属性必须在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’beanApplicationContext初始化时不会被预实例化,而叫做’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自装配的协作者
SpringIoC容器可以自装配Bean之间的协作关系。这意味着,可以让Spring通过检查BeanFactory的内容,来解析bean的协作关系。自装配有5种模式。自装配是针对bean定义的,因此,可以指定一些bean自装配,而另外一些则不需要。使用自装配模式,完全可能不需要指定属性或者构造器参数,这样可以少一些手工配置。在XML配置中,自装配模式通过使用<bean/>autowire属性来实现。参看,autowire允许的值列表:
 
 
 
 
 
 
 3.2. 自装配模式
模式
解释
no
不使用自装配模式。Bean的引用使用ref元素定义。这是默认方式,对于大多数开发者来说,建议不要修改这个默认配置,因为,显式的指定协作者的方式有更多的可控性和明确性。从某种意义上说,这是系统构造的标准方式。
byName
属性名方式自装配。容器会寻找和属性同名的bean来进行装配。例如,假设有一个bean被设置成这种方式,它包含一个master属性(也就是说,它有一个说它Master(...)方法),Spring会查询命名为masterbean,然后用它来设置属性。
byType
属性类型方式自装配。这种方式假定在容器中存在一个和属性类型相同的bean。如果在容器中有两个这样的bean存在,那么将会抛出致命异常,这种情况下是不允许使用这种方式的。如果,没有这样的bean,那么没有任何问题;该属性不被设置。如果需要有提示,那么将dependency-check属性设置为”objects”,那么这种情况下,将会抛出一个错误。
constructor
类似于byType方式,但是针对的是构造器参数。如果在容器中,不存在与构造器参数类型相同的bean,致命异常被抛出。
autodetect
通过bean类自己来选择constructor还是byType方式。如果bean采用的是默认的构造器,那么将会使用byType方式。
注意,对于propertyconstructor-arg的显式依赖设置会覆盖自装配模式。也要注意一点的是,自装配模式不适用与简单类型属性,如原始类型、StringsClasses类型(包括这些简单类型组成的数组类型)。(尽管这是应该被设计和考虑实现的一个特性)自装配模式应该和依赖检查结合起来,这样,在装配完成后,可以进行进一步的检查。
了解自装配模式的优劣也是很重要的。
下面是优点:
l         自装配模式可以大大减少手工配置内容。然而,在这方面,诸如bean模版等机制也可以做到(在后面讨论)。
l         自装配模式在对象发生更新的时候,可以自动的更新配置情况。例如,当需要为一个类增加额外的依赖时,那么通过自装配可以在不修改配置信息的情况下实现。因此,在开发期间,自装配模式什么有用,当代码基线逐步稳定以下,可以在转换成显式配置方式。
下面是缺点:
l         自装配模式相比而言更加具有不可确定性。尽管,在上面的表格中提到,Spring尽可能的避免因为猜测而导致不可预知的结果,但是Spring管理的那些对象之间的关系变得不再明确。
l         对于那些需要从Spring容器获取信息,然后产生描述信息的工具来说,装配过程信息不可利用;
l         当容器中只存在唯一一个与setter方法或者构造器参数类型匹配的bean定义的时候,自装配模式才能正常工作。如果存在任何潜在的不确定性,那么你最好还是进行显式装配。
具体使用哪种方式都没有“对”或者“错”。但在一个项目中,保持一致性还是最好的方式;例如,如果在项目中基本没有使用自装配模式,当你只是在一两个bean定义中采用这种方式的时候,很可能让人产生迷惑。
3.3.6.1 禁止Bean的自装配
你也可以禁止bean的自装配模式。当使用SpringXML配置bean时,<bean/>
'autowire-candidate'属性可以设置成’false’;这样,容器就会将该bean从自装配体系中排除出去。
当你需要指定某个bean禁止以自装配方式注射到其他bean的时候,这个属性就变得
很有用。当然,这并不是说,被排除的bean自身不能使用自装配方式...,而是说它自身不可以作为自装配的候选对象。
3.3.7依赖检查
 SpringIoC容器可以检查当前容器中的bean的未解析依赖的存在性。检查的内容包括那些没有在bean定义中设置值的bean属性,或者那些应该通过自装配模式设置的属性。
 当你需要确认bean的所有属性(或某种类型的所有属性)是否被设置时,该特性就显得很有用了。当然,大多数情况下,bean会有属性的默认值,或者有些属性在有些场景下不需要赋值,因此,该特性最好有节制的使用。依赖检查同样可以对于单独的bean进行配置。依赖检查包括几种不同的模式。使用XML配置时,通过指定'dependency-check'属性来实现。该属性可以取下面的值。
 
 
 3.3. 依赖检查模式
模式
解释
none
不执行依赖检查。即使bean属性没有赋值也没有关系。
simple
对于原始类型和集合类型属性的依赖检查。(不检查协作者类型依赖,如bean的引用)
object
对于协作者的依赖检查。
all
对于协作者、原始类型和集合类型的依赖检查。
如果是Java5Tiger)用户,可以参考源码级的注释,参考第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
 由于参数的数量大多数情况下足以区分每个可能的选择,这种方式可以节省许多的手工输入工作,你只需要手工输入足以表达参数类型的最短的字符串就可以了。
原创粉丝点击