IoC容器4——依赖

依赖

典型的企业级应用不可能仅仅由一个对象组成。即使是最简单的应用程序也拥有一些对象,它们相互协作展示给最终用户一个耦合的应用程序。下面将介绍如何从定义一些独立的bean到在一个完整应用中相互协作完成一个目标的一组对象。

1 依赖注入

依赖注入是一个过程,在此过程中对象通过构造函数参数、工厂方法参数或在对象构造或从工厂方法返回后设置设置来定义它们的依赖关系。然后容器在创建bean的时候注入这些依赖关系。整个过程是反向的,bean自身通过类的直接构造函数或Service Locater模式控制实例化和定位自己的依赖关系,因此也被称为控制反转。

使用DI原理,代码会更清晰,当对象具有依赖关系时可以更有效的解耦。对象不查找其依赖关系,并且不知道依赖关系的位置或类。因此,类会变得容易测试,特别是当依赖于接口或抽象基类时,允许在单元测试用使用村歌或模拟实现。

依赖注入主要使用两种方式,一种是基于构造函数的依赖注入,另一种的基于Setter方法的依赖注入。

基于构造函数的依赖注入

基于构造函数的依赖注入是通过容器调用带参数的构造函数完成的,每个参数代表一个依赖关系。调用指定参数的静态工厂方法构造bean几乎等价。下面的例子展示了一个只能通过构造函数依赖注入的例子。注意,这个类本身并没有什么特别的,它仅仅是一个POJO,并没有依赖容器指定的接口、基类或者注解。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a 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...

}

构造函数参数的解析

使用参数的类型进行构造函数参数解析。如果在bean定义时构造函数不存在潜在的歧义,那么构造函数参数在bean定义中定义的顺序就是在bean实例化时将这些参数提供给适当的构造函数的顺序。参考下面的代码

package x.y;

public class Foo {

    public Foo(Bar bar,Baz baz) {
        // ...
    }

}

假设Bar和Baz类没有继承关系,就不存在潜在的模糊性。因此下面的配置可以正常工作,不需要在 元素中指定构造函数参数的索引和类型。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

当引用其它的bean,类型是已知的,匹配会正常工作(参考上面的例子)。当要使用一个简单类型,例如 true ,Spring无法确定value的类型,所以无法正常匹配。考虑下面的例子:

package examples;

public class ExampleBean {

    // Number of years to 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;
    }

}

在上面的场景中,如果使用type属性置顶了构造函数参数的类型,容器就可以匹配简单类型。例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

使用index属性指定构造函数参数的顺序,例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引可以解析构造函数具有两个相同类型参数的歧义。请注意,索引从0开始。

也可以使用构造函数的参数名来消除value的歧义:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,为了使参数名消除歧义可以开箱即用,代码必须在启用debug标志饿情况下编译,以便Spring可以从构造函数中查找参数名。如果无法使用调试标志编译代码(或者不想),则可以使用@ConstructorProperties JDK注解来明确的命名构造函数参数。简单的例子如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years","ultimateAnswer"})
    public ExampleBean(int years,String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

基于Setter方法的依赖注入

基于Setter方法的依赖注入是由调用bean的setter方法完成的,这个过程发生在调用无參的构造方法或无參的静态工厂方法实例化bean之后。

下面的例子展示了一个可以仅仅使用纯Setter进行依赖注入的类。这个类是传统的java类,它仅仅是一个POJO,并没有依赖容器指定的接口、基类或者注解。

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 setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}

ApplicationContext支持使用基于构造函数的依赖注入和基于Setter方法的依赖注入来管理它的bean。它也支持在构造函数注入一部分以后关系后使用基于setter方法的依赖注入。开发者可以在BeanDeFinition中使用propertyeditor实例来自由选择注入的方式。但是大多数Spring用户不需要直接使用这些class,而是使用XML格式的bean定义、注解组件(使用@Controller、@Component等注解注释类)或者@Configuration注释的类中的@Bean注释的方法(Java的配置形式)。这些源代码会在内部被转换为BeanDeFinition的实例并用于加载整个Spring IoC容器的实例。

基于构造函数的依赖注入还是基于Setter方法的依赖注入?

由于可以混合使用两种依赖注入的方法,所以一个好的原则是对那些必需的依赖关系首选构造函数、可选的依赖关系使用setter方法或配置方法。在setter方法上使用@required注解使得属性变为必须的依赖关系。

Spring团队提倡使用基于构造函数的依赖注入,因为它可以将应用程序的组件实现为不可变的对象,并且保证必需的依赖关系不为空。此外,构造函数注入的组件总是会以完全初始化的状态放回给客户端(调用代码。附注,大量的构造函数参数是一个坏的代码风格,这意味着该类可能有太多的责任,应该重构将不同职能分离。

Setter依赖注入应主要用于可选的依赖关系,即可以在类中指定合理的认值。否则必须在使用依赖关系的每处代码做非空检查。setter依赖注入的好处是setter方法使得该类的对象可以在以后重新配置或重新注入。因此,通过JMX管理MBean是setter依赖注入的一个用例。

应当使用对特定类最用意义的依赖注入形式。当处理没有源码的第三方类时,由您自己选择。例如,如果第三方类没有暴露任何setter方法,那么构造函数注入可能是唯一可用的依赖注入形式。

依赖的解析过程

容器的依赖的解析过程如下:

  • 使用描述所有bean的配置元数据创建和初始化ApplicationContext。配置元数据可以通过XML文件、Java代码和注解指定。
  • 一个Bean的依赖通过属性、构造函数参数或者静态工厂方法的参数等来表示。这些依赖会在Bean创建的的时候注入和装载。
  • 一个属性或者构造函数的参数都是实际定义的值或者引用容器中其他的Bean。
  • 一个属性或者构造参数如果是一个实际定义的值,则可以由特定的类型转换成属性或构造函数的参数需要的类型。认的,Spring将String类型转成认的Java内在的类型,比如int,long,String,boolean等。

创建容器时,spring容器会验证每个bean的配置。但是,在bean实际创建之前不会设置bean属性。scope属性为singleton并设置为pre-instantiated(认)的Bean将在创建容器时被创建。其它情况下,只有在需要时才创建该bean。创建bean可能会导致创建一组bean,因为创建和分配了bean的依赖关系及其依赖关系的依赖关系(等等)。请注意,依赖关系无法匹配的问题可能会较晚出现,即在首次创建受影响的bean之时。

循环依赖

如果主要使用基于构造函数的依赖注入,那么很有可能出现一个无法解决的循环依赖的场景。比如类A通过构造函数注入一个类B的实例,而类B通过构造函数注入一个类A的实例。如果配置类A和类B的bean导致相互注入,Spring IoC容器会在运行时发现这种循环引用,并抛出BeanCurrentlyInCreationException异常。一个可行的解决办法是在一些类的源码中把构造函数注入改为setter注入。或者仅仅使用setter注入替代构造函数注入。尽管不被推荐,可以使用setter注入方法解决循环依赖。

一般可以信任Spring会做正确的事情。它会在容器加载时发现配置问题,例如引用的bean不存在或者循环引用。Spring会尽可能晚的设置属性和引用,当bean实际被创建时。这意味着容器加载成功会也会产生异常,当被请求的bean由于自身或者它的引用创建出现问题时。例如,bean因为缺少属性或者类型错误时抛出异常。这可能会导致一些配置问题被延迟发现,这就时为什么ApplicationContext的实现认使用预加载、单例模式的bean。以实际需要之前创建这些bean的一些预付时间和内存为代价,可以在创建ApplicationContext时发现配置问题,而不是以后。依旧可以覆盖认的行为,将singleton从预加载设置为懒加载。

如果不存在循环依赖,当一个或多个协作bean被注入到bean中时,每个协作bean在被注入bean之前被完全配置。举例来说,如果Bean A依赖于Bean B,那么Spring IoC容器会先配置Bean B,然后调用Bean A的Setter方法。换言之,Bean先会实例化,然后注入依赖,然后才是相关的生命周期方法(例如 configured init method 或者 InitializingBean callback method))的调用

依赖注入的例子

下面是使用XML配置的setter依赖注入的例子,仅仅一小段Spring XML配置文件定义了一些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;
    }

}

在上面的例子当中,Setter方法的声明和XML文件中的属性相一致,下面的例子是基于构造函数的依赖注入:

<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的构造函数的参数。

下面的例子使用静态工厂方法返回类的实例:

<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,int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }

}

使用 元素指定静态工厂方法的参数,与使用构造函数注入相同。返回实例的类型和包含工厂方法的类不需要相同,尽管上例中是相同的。实例(非静态)工厂方法以基本相同的方式被使用(除了使用factory-bean属性替代class属性)。

2 依赖和配置细节

如上文所述,可以将bean属性和构造函数参数设置为其它被管理的bean的引用或者行间定义的值。Spring XML 格式的配置元数据使用 子元素完成上述配置。

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

的value属性以可读的字符串形式定义一个属性或者构造函数参数。Spring的conversion service将这些值从字符串转换属性或构造函数参数需要的正确类型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <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="masterkaoli"/>
</bean>

下面的例子使用了p命名空间,使得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
    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>

上面的XML更加简洁,但是输入错误只能在运行时发现而不是设计阶段,除非使用了IntelliJ IDEA 或者 Spring Tool Suite(STS)这类在创建bean定义时支持自动补全的IDE。强烈推荐这种IDE助手。

可以配置一个java.util.Properties实例如下:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.MysqL.jdbc.Driver
            jdbc.url=jdbc:MysqL://localhost:3306/mydb
        </value>
    </property>
</bean>

spring容器使用propertyeditor元素中的文字转换为java.util.Properties实例。这是一个很好的捷径,而且是Spring团队赞成的使用嵌套 元素替代value属性的几个场景之一。

idref 元素

idref元素是一种带有错误检查的方式,把容器中另外一个bean的id(字符串的值,不是引用)赋值给 元素。

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上面的代码段与下面的代码段作用等价(运行时):

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

第一种形式优于第二种形式,因为使用idref标签可以使容器在部署时减产被引用的命名bean确实存在。使用第二种方法,不会检查赋给targetName属性的值。拼写错误(可能是致命的)只有bean实际被实例化是才能被发现。如果是一个prototypebean,拼写错误和由此导致的异常可能会在容器部署很久以后才会出现。

idref 的local属性在4.0的beans xsd不再被支持,因为不再提供一个 ref bean 的引用值。如果要升级到4.0 schema 使用idref bean替代idref local即可。

一个常见的使用场景是(至少在Spring 2.0之前的版本中),在Proxyfactorybean的定义中使用 元素配置AOP拦截器。指定拦截器的名称时使用 元素可以防止拼写错误

引用其它bean

元素作为 元素和 的子元素,不能再被嵌套。它用来将容器管理的另一个bean的引用赋值给bean的特定属性。引用的bean是其属性将被设置的bean的依赖关系,并且在设置属性之前根据需要初始化它。(如果被引用的bean是singleton,它也许已经被容器实例化了。)所有的引用都指向其它对象。范围和验证依赖于是否通过bean,local或者parent属性指定了另一个对象的id或name.

通过 元素的bean属性指定目标bean是最常见的方式,它允许创建一个引用指向在同一容器或父容器的任何一个bean,不管是否在同一XML文件中配置。bean属性的值可以是目标bean的id属性或者name属性值之一。

<ref bean="someBean"/>

通过parent属性创建一个bean的引用,仅可以指向父容器中的bean。属性的值可以是目标bean的id属性或者name属性值之一,并且目标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" <!-- bean name is the same as 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 here -->
</bean>

在4.0的beans xsd中 ref元素的local属性不再支持,因为不提供regular bean的引用值。升级到4.0schema,把ref local改为ref bean即可。

inner bean

元素中的 元素定义 inner bean。

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

inner bean 的定义不需要定义id或者name;如果指定,容器也不使用其作为标识。容器在创建inner bean时同样会忽略scope标识:inner bean 总是匿名的并且它们总是与outer bean一起创建。将inner bean注入到除了包裹它的bean之外的协作bean中,或者独立访问它们是不可能的。可以从自定义范围接收销毁回调;例如对于包含在singleton bean中的request-scoped inner bean:创建inner bean的实例会将其与包含它的bean绑定,但是销毁回掉函数允许它共享request 范围的生命周期。这不是一个常见的场景;inner bean一般仅仅共享包含它的bean的范围。

元素中,可以设置Java Collection类型的属性或参数,分别对应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@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.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="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </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,或者是集合的value都可以配置为下列之中的一些元素:

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

集合合并

Spring 容器也支持集合合并。可以定义父 元素,并使用子元素继承和覆盖父集合中的值。即子集合的值是父集合和子集合元素合并(子集合元素覆盖父集合元素中指定的值)后的结果。

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

<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">
            <!-- the merge is specified on the child collection deFinition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

注意,子Bean定义中的adminemails中的 元素使用了merge=true属性。当子Bean由容器解析并实例化时,生成的实例具有一个adminemails属性集合,该集合包含将该子Bean的adminemails集合与父级的adminemails集合合并的结果。

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

子Properties集合的值继承了所有父 的值,并且子元素support的值覆盖了父集合中的值。

这种合并行为与 集合类型相似。在 元素情况下,维护与List集合类型相关联的语义,即有序的值集合的概念:父项的值先于所有子列表的值。而对于Map、Set和Properties集合类型,是没有顺序的。因此,对于在容器内部使用Map,Set和Properties的集合类型的实现,是没有顺序语义的。

集合合并的限制

不能合并不同类型的集合(例如合并Map和List),如果这么做了会抛出一个相关异常。merge属性必需在低级的、继承的子定义中使用;在父集合定义中指定merge属性是多余的,不会产生期望的合并。

强类型集合

通过Java 5 中引入的范型,可以使用强类型集合。即可以定义一个只包含String元素的集合类型。如果使用Spring依赖性注入强类型的集合到一个bean中,那么可以利用Spring的类型转换支持,以便将强类型的Collection实例的元素转换为适当的类型,然后添加到集合。

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类型。

Null 和 空字符串值

Spring 将空属性值作为空字符串处理。下面的XML格式的配置元数据代码段将email属性设置为空字符串。

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

上面的例子等价于下面的代码

exampleBean.setEmail("")

元素被解析为null。例如:

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

上面的例子等价于下面的代码

exampleBean.setEmail(null)

使用 p 命名空间精简XML

p 命名空间使你能够使用bean元素的属性代替嵌入的 元素描述属性值和/或协作bean。

Spring支持具有命名空间的可扩展配置格式,它们基于XML模式定义。上面讨论的beans配置格式在XML Schema文档中定义。但是,p命名空间没有在XSD文件中定义,只存在于Spring的核心。

下面例子中的两个代码段会被解析为相同的结果:第一个使用标准XML格式,第二个使用 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="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>

该示例显示了在bean定义中名为email的p命名空间中的属性。它告诉Spring包含一个属性生命。如前所述,p命名空间没有模式定义,因此可以将XML元素的属性名设置为类中的属性名称

下面的例子包含了两个引用其它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
        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引用使用 创建从bean john到bean jane的引用,第二个bean定义使用p:spouse-ref="jane"作为一个属性来执行相同的事情。在这种情况下,spouse是属性名,-ref 部分表示它不是一个直接值而已一个指向其它bean的引用。

p命名空间不如标准XML格式那么灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不会。建议仔细选择方法,并将其传达给团队成员,以避免同时生成使用所有三种方法的XML文档。

使用 c 命名空间精简XML

与p命名空间相似,在Spring 3.1 被引入的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"/>

    <!-- Traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

c命名空间与p有相同的约定(以-ref的属性用于bean引用),c命名空间用于通过名称来设置构造函数参数。同样,它也需要声明,即使它没有在XSD模式中定义(但它存在于Spring Core中)。

对于构造函数参数名称不可用的罕见情况(通常发生在没有调试信息的情况下编译字节码),可以使用参数索引:

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

由于XML语法,需要在索引符号前加_,因为XML属性名称不能以数字开头(即使某些IDE允许)。

在实践中,构造函数解析机制在匹配参数方面非常有效,所以除非真的需要,建议使用参数名称进行配置。

复合属性

当设置bean属性时,可以使用复合或嵌套属性名称,只要路径上所有的组件(除了最后一个属性)不为null。考虑下面的bean定义:

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

foo bean 有一个fred属性,fred有一个bob属性,而bob有一个sammy属性,并且最终的sammy属性的值将被设置为123。为了使它能够工作,在bean被创建之后foo的fred属性、fred的bob属性必须不能为空,否则NullPointerException异常将被抛出。

3 使用 depends-on

如果一个bean是另一个的依赖,通常意味着一个bean被设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的 元素来完成此任务。但是,有时bean之间的依赖关系并不直接:例如触发一个类中的静态初始化方法,像数据库驱动注册。depends-on属性可以强制指定一个或多个bean在使用这个属性的bean实例化之前实例化。下面的示例使用depends-on属性表示多单个bean的依赖关系:

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

要表示对多个bean的依赖,请使用逗号,空格和分号作为有效分隔符提供一个bean名称列表作为depends-on属性的值:

<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" />

bean定义中的depend-on属性可以同时指定初始化时间依赖关系和相应的销毁时间依赖关系(在singleton Bean的情况下)。定义depends-on属性的bean在被依赖的bean之前被销毁。因此 depends-on 也可以控制关​销毁顺序。

4 懒加载 bean

认情况下,ApplicationContext的实现在初始化过程中创建和配置所有singleton bean。一般来说,这种预实例化是可取的,因为配置或环境中的错误会被立即发现,而不是几个小时甚至几天之后。如果不想采用这种行为,可以通过将bean定义标记为懒加载来替代singleton的预实例化。懒加载bean通知IoC容器在首次请求时创建而不是容器启动时。

在XML配置中,这种行为被 元素的lazy-init属性控制,例如:

<bean id="lazy" class="com.foo.ExpensivetoCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

当上述配置被ApplicationContext使用时,启动时名为lazy的bean不会被预先实例化,而not.lazy bean则被预先实例化。

然后,当一个懒加载bean不一个非懒加载bean依赖时,ApplicationContext会在启动时创建懒加载bean,因为它必须满足单例的依赖关系。懒加载bean被注入到一个单例bean,那么它就不再是懒加载了。

也可以控制容器级别的懒加载,通过使用 的default-lazy-init属性。例如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

5 自动装配协作者

Spring 容器可以自动装配协作的bean。可以通过检查ApplicationContext的内容来允许Spring自动解析您的bean的协作者。自动装配有以下优点:

  • 自动装配可以显著减少指定属性或构造函数参数的需要。(其他机制,如bean模板在这方面也很有价值。)
  • 自动装配可以随着对象的发展而更新配置。例如,如果您需要向类添加依赖关系,则可以自动满足该依赖关系,而无需修改配置。因此,自动装配在开发过程中特别有用,无需显式指定而使代码库变得更加稳定。

在XML格式的配置元数据中,需要使用 元素的autowired属性指定bean定义的自动装配模式。自动装配有四种模式。可以指定每个Bean的装载方式,这样Bean就知道如何加载自己的依赖。

  • no (认)不装配。bean的引用必须通过ref元素定义。对于较大的项目部署,不建议修改认的配置,因为明确指定合作者给予更大的控制和清晰度。在某种程度上它记录了系统的结构。

  • byName 使用name属性自动装配。Spring为需要自动装配属性查找与属性具有相同名字的bean。例如,一个bean定义设置了按名字自动装配,并且它包含一个master属性(即它有setMaster(...)方法),Spring会查找名字为master的bean定义,并使用它给属性赋值。

  • byType 在容器中只有一个属性类型相同的bean存在时允许自动装配这个属性。如果有多个存在,会抛出致命的异常,提示对这个bean应使用byType模式的自动装配。如果没有相同类型的bean,则什么都不做,该属性不会被赋值。

  • constructor 类似于byType,但应用与构造函数参数。如果容器中没有与构造函数参数类型匹配的bean,会产生一个致命的错误

使用byType或constructor自动装配模式,可以装配数组和强类型集合。在这种情况下容器中所有符合类型的bean都会被自动装配(作为数组或集合的元素)。如果键的类型为Sring,可以自动装配强类型Map。一个自动装配的Map的值会包含容器中所有相同类型的实例,Map的键是相应类型的name。

可以将自动装配行为与依赖关系检查相结合,依赖关系检查是在自动装配完成后执行的。

自动装配的局限性和缺陷

自动装载如果在整个的项目的开发过程中使用,会工作的很好。但是如果不是全局使用,而只是用之来自动装配几个Bean的话,会很容易迷惑开发者。

下面是一些自动装配的局限性和缺陷:

  • 精确的property以及constructor-arg参数配置,会覆盖掉自动装配的配置。开发不能够自动装配简单属性,比如基本类型字符串、Class类的对象(和这些类型组成的数组)。这是设计上的限制。

  • 自动装配并有精确装配准确。尽管如上表所述,Spring会很小心的避免在出现歧义情况下猜测,但是由Spring管理的对象之间的关系不再被明确记录。

  • 对于从spring容器生成文档的工具,可能无法获得装配信息。

  • 容器中的多个bean定义可能由setter方法参数或构造函数参数的类型的匹配来进行自动装配。对于数组、集合或者Map,这也许不是问题。然而对于单个值的依赖关系,这种模糊性是不能轻易解决的。如果没有唯一的bean定义可用,则会抛出异常。

在后面的场景,开发者有如下的选择:

  • 放弃自动装配而使用精确装配;
  • 在bean定义中通过配置autowire-candidate属性为false来阻止自动装配
  • 通过配置 元素的primary属性为true来指定这个bean为主要的候选Bean;
  • 使用基于注释的配置实现更细粒度的控制。

自动装配中排除一个bean

在每个bean的基础上,可以将bean从自动装配中排除。在Spring的XML格式中,将 元素的autowire-candidate属性设置为false;容器使特定的bean定义不可用于自动装配架构(包括注释配置,例如@Autowired)。

autowire-candidate属性被设计为仅影响基于类型的自动装配。它不会影响名称的显式引用,即使指定的bean的autowired-candidate设置为false。因此,如果名称匹配,则通过名称自动装配将会注入一个bean。

开发者可以通过模式匹配而不是Bean的名字来限制自动装配的候选者。可以在最上层的 元素的default-autowire-candidates属性中来配置多种模式。比如,限制自动装配候选者的名字以Repository结尾,可以设置为*Repository。如果需要配置多种模式,只需要用逗号分隔开即可。当然Bean中如果配置了autowire-candidate的话,这个信息拥有更高的优先级,模式匹配规则不会应用与这些bean。

这项技术有助于配置那些不需要由自动装配注入到其它bean中的bean。一个被排除的bean不意味着它自身不能使用自动装配。相反,仅仅是它自身并非自动装配其它bean的候选者。

方法注入

在大多数应用场景中,容器中的大多数bean都是singleton。当一个singleton bean需要与另一个singleton bean协作时,或者一个非singleton bean需要与另一个非singleton bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean生命周期不同时,会出现问题。假设单例bean A需要使用非单例(prototype)bean B,也许在A的每个方法调用上。容器只创建单例bean A一次,因此只能获得一个设置属性的机会。容器不能在每次需要的时候为bean A提供一个新的bean B实例。

一种解决方案是放弃一部分控制反转。可以通过实现ApplicationContextAware接口使 Bean A可以看到ApplicationContext,并且通过对容器的getBean(“B”)调用在每次bean A需要B时获取B的实例(通常是新的)。以下是这种方法一个例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    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();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command",Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上述方法是不可取的,因为业务代码可见ApplicationContext并耦合到Spring框架。方法注入是Spring IoC容器的一个先进的功能,可以以干净的方式处理这种情况。

查询方法注入

查询方法注入spring容器覆盖它管理的bean的方法、来返回查找的另一个容器中的Bean的一种能力。一般在上述场景中查找返回prototype bean。Spring框架通过使用cglib库中的字节码生成功能来动态生成覆盖该方法的子类来实现此方法注入。

  • 为了能使这种动态子类方法工作,spring容器要动态生成子类的父类不能为final,被覆盖的方法也不能为final。
  • 单元测试具有抽象方法的类需要您自己对类进行子类化,并提供抽象方法的存根实现。
  • 组件扫描也需要具体的方法,这需要具体的类来进行。
  • 一个关键的限制是,查找方法不适用于工厂方法,不适用于在配置类中的@Bean方法,因为在该情况下容器不负责创建实例,因此无法创建运行时生成的子类。

下面代码片段中的CommandManager类,spring容器将动态地覆盖createCommand()方法。CommandManager类将不会有任何Spring依赖关系:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // 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();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

需要注入的方法需要有以下的签名格式:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是抽象的,动态生成的子类会实现这个方法;如果方法不是抽象的,动态生成的子类会覆盖这个方法

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" 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="myCommand"/>
</bean>

标识为commandManager的bean只要需要myCommand bean的新实例,就调用它自己的方法createCommand()。必须部署myCommand bean作为prototype。如果它是singleton,则每次返回myCommand bean的同一个实例。

或者在基于注释的组件模型中,可以通过@Lookup注释声明一个查找方法

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或者,更为惯用的是,针对lookup方法的返回值的类型来解析目标bean:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

请注意,通常会使用具体的Lookup方法,以使它们与Spring的组件扫描规则兼容(抽象类认情况下被忽略)。此限制不适用于明确注入或明确导入bean类的情况。

另一种访问不同范围的目标bean的方法是ObjectFactory / Provider注入点。

还可以使用ServiceLocatorfactorybean(在org.springframework.beans.factory.config包中)。

任意方法替换

相比于查找方法注入,使用另一个方法实现替换bean中任意方法是一种不常用的方法注入形式。

使用基于XML的配置元数据,可以使用replacement-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(String)
 * 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"/>

可以在 元素中使用一个或多个 元素来指定被覆盖方法的签名。只有当方法重载时,参数的签名才是必需的。为方便起见,参数的类型字符串可能是完全限定类型名称的子字符串。例如,以下全部匹配java.lang.String:

java.lang.String
String
Str

由于参数的数量通常足以区分每个可能的选择,所以一般只需键入与参数类型匹配的最短字符串即可。

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...