2.4 Spring Framework 5.x之DI(依赖注入)

依赖注入(DI)

依赖注入(DI)是一个过程,通过这个过程,对象只能通过构造函数参数,工厂方法的参数或在构造对象实例后在对象实例上设置的属性来定义它们的依赖关系(即,它们使用的其他对象)。从工厂方法返回。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的反向(因此叫做控制反转),通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。

使用DI原则的代码更清晰,当对象提供其依赖项时,解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

DI存在两个主要变体:基于构造函数的依赖注入和基于Setter的依赖注入。

基于构造函数的依赖注入

基于构造函数的DI由容器调用具有多个参数的构造函数来完成,每个参数表示一个依赖项。调用static具有特定参数的工厂方法来构造bean几乎是等效的,本讨论同样处理构造函数和static工厂方法的参数。以下示例显示了一个只能使用构造函数注入进行依赖注入的类:

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...
}

请注意,这个类没有什么特别之处。它是一个POJO,它不依赖于容器特定的接口,基类或注解。

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

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假设ThingTwo并且ThingThree类与继承无关,则不存在潜在的歧义。因此,以下配置工作正常,您不需要在 元素中显式指定构造函数参数索引或类型。

<beans>
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
    </bean>

    <bean id="thingTwo" class="x.y.ThingTwo"/>

    <bean id="thingThree" class="x.y.ThingThree"/>
</beans>

当引用另一个bean时,类型是已知的,并且可以进行匹配(与前面的示例一样)。当使用简单类型时,例如 true,Spring无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。考虑以下课程:

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开始。

构造函数参数名称

您还可以使用构造函数参数名称进行值消歧,如以下示例所示:

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

请记住,为了使这项工作开箱即用,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用debug标志编译代码,则可以使用 @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的依赖注入

在调用无参数构造函数或无参数static工厂方法来实例化bean之后,基于setter的DI由bean上的容器调用setter方法完成。

以下示例显示了一个只能通过使用纯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方法的DI为它所管理的Bean类。在通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以以a的形式配置依赖项,并将BeanDefinition其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户不直接与这些类(即,编程),而是用XML bean 定义注解的组件(也就是带注解类@Component, @Controller等),或者@Bean方法在基于Java的@Configuration类。然后,这些源在内部转换为实例BeanDefinition并用于加载整个Spring IoC容器实例。

基于构造函数或基于setter的DI

由于您可以混合基于构造函数和基于setter的DI,因此将构造函数用于强制依赖项和setter方法或可选依赖项的配置方法是一个很好的经验法则。请注意, 在setter方法上使用@Required注释可用于使属性成为必需的依赖项。

Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保不需要所需的依赖项null。此外,构造函数注入的组件总是以完全初始化的状态返回到客户端(调用)代码。作为旁注,大量的构造函数参数是一个糟糕的代码气味,暗示该类可能有太多的责任,应该重构以更好地解决关注点的正确分离。

Setter注入应主要仅用于可在类中指定合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何位置执行非空检查。setter注入的一个好处是setter方法使该类的对象可以在以后重新配置或重新注入。因此,通过JMX MBean进行管理是二次注入的一个引人注目的用例。

使用对特定类最有意义的DI样式。有时,在处理您没有源的第三方类时,会选择您。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是唯一可用的DI形式。

依赖性解决过程

容器执行bean依赖性解析,如下所示:

  • 使用ApplicationContext描述所有bean的配置元数据创建和初始化。配置元数据可以由XML,Java代码或注释指定。

  • 对于每个bean,它的依赖关系以属性,构造函数参数或static-factory方法的参数的形式表示(如果使用它而不是普通的构造函数)。实际创建bean时,会将这些依赖项提供给bean。

  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。

  • 作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够转换成字符串格式提供给所有内置类型的值,例如int, long,String,boolean,等等。

Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。创建容器时会创建单例作用域并设置为预先实例化(默认值)的Bean。范围在Bean范围中定义。否则,仅在请求时才创建bean。创建bean可能会导致创建bean的图形,因为bean的依赖关系及其依赖关系(依此类推)被创建和分配。请注意,这些依赖项之间的解决方案不匹配可能会显示较晚 - 也就是说,首次创建受影响的bean时。

循环依赖

如果您主要使用构造函数注入,则可以创建无法解析的循环依赖关系场景。

例如:类A通过构造函数注入需要类B的实例,而类B通过构造函数注入需要类A的实例。如果将A类和B类的bean配置为相互注入,则Spring IoC容器会在运行时检测此循环引用,并抛出一个 BeanCurrentlyInCreationException。

一种可能的解决方案是编辑由setter而不是构造函数配置的某些类的源代码。或者,避免构造函数注入并仅使用setter注入。换句话说,尽管不推荐使用,但您可以使用setter注入配置循环依赖关系。

与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖强制其中一个bean在完全初始化之前被注入另一个bean(经典的鸡与鸡蛋场景)。

你通常可以信任Spring做正确的事。它在容器加载时检测配置问题,例如对不存在的bean和循环依赖的引用。当实际创建bean时,Spring会尽可能晚地设置属性并解析依赖项。这意味着,如果在创建该对象或其中一个依赖项时出现问题,则在请求对象时,正确加载的Spring容器可以在以后生成异常 - 例如,bean因缺少或无效而抛出异常属性。这可能会延迟一些配置问题的可见性ApplicationContext默认情况下实现预实例化单例bean。以实际需要之前创建这些bean的一些前期时间和内存为代价,您ApplicationContext会在创建时发现配置问题,而不是更晚。您仍然可以覆盖此默认行为,以便单例bean可以懒惰地初始化,而不是预先实例化。

如果不存在循环依赖,当一个或多个协作bean被注入依赖bean时,每个协作bean在被注入依赖bean之前完全配置。这意味着,如果bean A依赖于bean B,则Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果它不是预先实例化的单例),设置其依赖项,并调用相关的生命周期方法(如配置的init方法 或InitializingBean回调方法)。

依赖注入的示例

以下示例将基于XML的配置元数据用于基于setter的DI。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"/>

以下示例显示了相应的ExampleBean类:

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文件中指定的属性匹配。以下示例使用基于构造函数的DI:

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

以下示例显示了相应的ExampleBean类:

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调用static工厂方法来返回对象的实例:

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

以下示例显示了相应的ExampleBean类:

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

static工厂方法的参数由元素提供,与实际使用的构造函数完全相同。工厂方法返回的类的类型不必与包含static工厂方法的类相同(尽管在此示例中,它是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用factory-bean属性而不是class属性),因此我们不在此讨论这些细节。

1.4.2 依赖关系和配置细节

如上一节所述,您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者作为内联定义的值。Spring的基于XML的配置元数据为此目的支持其元素和元素中的子元素类型。

直值(基元,字符串等)

元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。 Spring的转换服务用于将这些值从String转换为属性或参数的实际类型。 以下示例显示了要设置的各种值:

<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更简洁。但是,除非您在创建bean定义时使用支持自动属性完成的IDE(例如IntelliJ IDEA或Spring Tool Suite),否则会在运行时而不是设计时发现拼写错误。强烈建议使用此类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容器通过使用JavaBeans 机制将元素内的文本转换为 java.util.Properties实例PropertyEditor。这是一个很好的快捷方式,并且是Spring团队支持在value属性样式上使用嵌套元素的少数几个地方之一。

该idref元素

该idref元素只是一种防错方法,可以将id容器中另一个bean 的(字符串值 - 而不是引用)传递给一个或 元素。以下示例显示了如何使用它:

<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标签可以让容器在部署时验证引用的命名bean实际存在。在第二个变体中,不对传递给bean 的targetName属性的值执行验证client。只有在client实际实例化bean 时才会发现错别字(很可能是致命的结果)。如果client bean是原型 bean,则只能在部署容器后很长时间才能发现此错误和产生的异常。

注意:
4.0 beans XSD不再支持local该idref元素 的属性,因为它不再提供常规bean引用的值。升级到4.0架构时,将现有idref local引用更改idref bean为。

其中一个共同的地方(至少在早期比Spring 2.0版本)元素带来的值在配置AOP拦截在 ProxyFactoryBeanbean定义。指定拦截器名称时使用元素可以防止拼写错误的拦截器ID。

参考其他Beans(合作者)
所述ref元件是内部的最终元件或 定义元素。在这里,您将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。引用的bean是要设置其属性的bean的依赖项,并且在设置该属性之前根据需要对其进行初始化。(如果协作者是单例bean,它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。划定范围和有效性取决于是否通过指定其他对象的ID或名称bean,local,或parent属性。

通过标记的bean属性指定目标bean 是最通用的形式,并允许创建对同一容器或父容器中的任何bean的引用,而不管它是否在同一XML文件中。bean属性的值 可以id与目标bean 的属性相同,或者与目标bean的name属性中的值之一相同。以下示例显示如何使用ref元素:

<ref bean="someBean"/>

通过该parent属性指定目标bean 会创建对当前容器的父容器中的bean的引用。parent 属性的值可以id与目标bean 的属性或目标bean的name属性中的值之一相同。目标bean必须位于当前bean的父容器中。您应该使用此bean引用变量,主要是当您具有容器层次结构并且希望将现有bean包装在父容器中时,该父容器具有与父bean同名的代理。以下一对列表显示了如何使用该parent属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.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不再支持local该ref元素 的属性,因为它不再提供常规bean引用的值。升级到4.0架构时,将现有ref local引用更改ref bean为。

内部Beans

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

内部bean定义不需要定义的ID或名称。如果指定,则容器不使用此类值作为标识符。容器还会scope在创建时忽略标志,因为内部bean始终是匿名的,并且始终使用外部bean创建。不可能独立访问内部bean或将它们注入协作bean而不是封闭bean。

作为一个极端情况,可以从自定义作用域接收销毁回调 - 例如,对于包含在单例bean中的请求作用域内部bean。内部bean实例的创建与其包含bean相关联,但是销毁回调允许它参与请求范围的生命周期。这不是常见的情况。内部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">[email protected]</prop>
            <prop key="support">[email protected]</prop>
            <prop key="development">[email protected]</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>

映射键或值的值或设置值也可以是以下任何元素:

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

合并合并

Spring容器还支持合并集合。应用程序开发人员可以定义父,,或元素,并有孩子,,或元素继承和父集合覆盖值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素覆盖父集合中指定的值。

关于合并的这一部分讨论了父子bean机制。不熟悉父母和子bean定义的读者可能希望在继续之前阅读 相关部分。

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">[email protected]</prop>
                <prop key="support">[email protected]</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">[email protected]</prop>
                <prop key="support">[email protected]</prop>
            </props>
        </property>
    </bean>
<beans>

请注意在子bean定义的adminEmails属性的元素上使用merge = true属性。 当容器解析并实例化子bean时,生成的实例有一个adminEmails属性集合,其中包含将子项的adminEmails集合与父项的adminEmails集合合并的结果。 以下清单显示了结果:

[email protected]
[email protected]
[email protected]

孩子Properties集合的值设置继承父所有属性元素,和孩子的为值support值将覆盖父集合的价值。

这一合并行为同样适用于,和 集合类型。在元素的特定情况下,保持与List集合类型(即,ordered 值集合的概念)相关联的语义。父级的值位于所有子级列表的值之前。在的情况下Map,Set和Properties集合类型,没有顺序存在。因此,没有排序的语义在背后的关联的集合类型的效果Map,Set以及Properties该容器内部使用实现类型。

收集合并的局限性

您无法合并不同的集合类型(例如a Map和a List)。如果您尝试这样做,Exception则会引发相应的操作。merge必须在较低的继承子定义上指定该属性。merge在父集合定义上指定属性是多余的,并且不会导致所需的合并。

Strongly-typed collection 强类型集合

通过在Java 5中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一种Collection类型,使得它只能包含(例如)String元素。如果使用Spring将强类型依赖注入Collection到bean中,则可以利用Spring的类型转换支持,以便强类型Collection 实例的元素在添加到之前转换为适当的类型Collection。以下Java类和bean定义显示了如何执行此操作:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <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>    

当为注入准备bean 的accounts属性时,通过反射可获得something关于强类型的元素类型的泛型信息Map<String, Float>。因此,Spring的类型转换基础结构将各种值元素识别为类型Float,并将字符串值(9.99, 2.75,和 3.99)转换为实际Float类型

空字符串值和空字符串值

Spring将属性等的空参数视为空Strings。以下基于XML的配置元数据片段将email属性设置为空 String值(“”)。

<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命名空间的XML快捷方式

p-namespace允许您使用bean元素的属性(而不是嵌套 元素)来描述属性值协作bean,或两者。

Spring支持具有命名空间的可扩展配置格式,这些命名空间基于XML Schema定义。beans本章中讨论的配置格式在XML Schema文档中定义。但是,p-namespace未在XSD文件中定义,仅存在于Spring的核心中。

以下示例显示了两个XML片段(第一个使用标准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="[email protected]"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[email protected]"/>
</beans>

该示例显示email了bean定义中调用的p命名空间中的属性。这告诉Spring包含一个属性声明。如前所述,p命名空间没有架构定义,因此您可以将属性的名称设置为属性名称。

下一个示例包括另外两个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-namespace的XML Shortcut类似,Spring 3.1中引入的c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套constructor-arg元素。

以下示例使用c:命名空间执行与 基于构造函数的依赖注入相同的操作:

<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="thingOne" class="x.y.ThingTwo"/>
    <bean id="thingTwo" class="x.y.ThingThree"/>

    <!-- traditional declaration -->
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
        <constructor-arg value="[email protected]"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="thingOne" class="x.y.ThingOne" c:thingTwo-ref="thingTwo" c:thingThree-ref="thingThree" c:email="[email protected]"/>

</beans>

该c:命名空间使用相同的约定作为p:一个(尾部-ref的bean引用),供他们的名字设置构造函数的参数。类似地,它需要声明,即使它没有在XSD架构中定义(它存在于Spring核心内)。

对于构造函数参数名称不可用的罕见情况(通常如果字节码是在没有调试信息的情况下编译的),您可以使用回退到参数索引,如下所示:

<!-- c-namespace index declaration -->
<bean id="thingOne" class="x.y.ThingOne" c:_0-ref="thingTwo" c:_1-ref="thingThree"/>

由于XML语法,索引表示法要求存在前导_,因为XML属性名称不能以数字开头(即使某些IDE允许它)。

实际上,构造函数解析 机制在匹配参数方面非常有效,因此除非您确实需要,否则我们建议在整个配置中使用名称表示法。

复合属性名称

设置bean属性时,可以使用复合或嵌套属性名称,只要除最终属性名称之外的路径的所有组件都不是null。考虑以下bean定义:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

该somethingbean具有一个fred属性,该属性具有属性,该bob属性具有sammy 属性,并且最终sammy属性的值设置为123。为了使其工作,在构造bean之后,fred属性something和bob属性fred不得为null。否则,NullPointerException抛出一个。

1.4.3 使用 depends-on

如果bean是另一个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" />

该depends-on属性既可以指定初始化时间依赖性,也可以指定单独的 bean,相应的销毁时间依赖性。depends-on在给定的bean本身被销毁之前,首先销毁定义与给定bean 的关系的从属bean 。这样,depends-on也可以控制关…机顺序。

1.4.4 延迟初始化的Bean

默认情况下,ApplicationContext实现会马上创建和配置所有单例 bean作为初始化过程的一部分。通常,这种预先实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后。如果不希望出现这种情况,可以通过将bean定义标记为延迟初始化来阻止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。

在XML中,此行为由 元素lazy-init上的属性控制,如以下示例所示:

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

当ApplicationContext使用前面的配置时,在ApplicationContext启动时,不会急切地预先实例化延迟初始化的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>

1.4.5 Autowiring

Spring容器可以自动连接协作bean之间的关系。您可以让Spring通过检查bean的内容自动为您的bean解析协作者(其他bean)ApplicationContext。自动装配具有以下优点:

自动装配可以显着减少指定属性或构造函数参数的需要。(在本章其他地方讨论的其他机制,如bean模板 ,在这方面也很有价值。)

自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,自动装配在开发期间尤其有用,而不会在代码库变得更稳定时否定切换到显式布线的选项。

使用基于XML的配置元数据(请参阅依赖注入)时,可以使用元素的autowire属性为 bean定义指定autowire模式。自动装配功能有四种模式。您指定每个bean的自动装配,因此可以选择要自动装配的那些。下表描述了四种自动装配模式:
表2.自动装配模式

模式 说明
no (默认)无自动装配。Bean引用必须由ref元素定义。不建议对较大的部署更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName 按属性名称自动装配。Spring查找与需要自动装配的属性同名的bean。例如,如果bean定义按名称设置为autowire并且它包含一个master属性(即,它有一个 setMaster(…)方法),则Spring会查找名为bean的定义master并使用它来设置属性。
byType 如果容器中只存在一个属性类型的bean,则允许属性自动装配。如果存在多个,则抛出致命异常,这表示您可能不会byType对该bean使用自动装配。如果没有匹配的bean,则不会发生任何事情(未设置该属性)。
constructor 类似byType但适用于构造函数参数。如果容器中没有构造函数参数类型的一个bean,则会引发致命错误。

使用byType或constructor autowiring mode(自动装配模)式,您可以连接阵列和键入的集合。在这种情况下,提供容器内与预期类型匹配的所有autowire候选者以满足依赖性。Map如果预期的键类型是,则可以自动装配强类型实例String。自动装配Map 实例的值由与预期类型匹配的所有bean实例组成, Map实例的键包含相应的bean名称。

自动装配的局限和缺点

自动装配在项目中一致使用时效果最佳。如果一般不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会让人感到困惑。

考虑自动装配的局限和缺点:

  • 显式依赖项property和constructor-arg设置始终覆盖自动装配。您不能自动装配简单属性,例如基元 Strings,和Classes(以及此类简单属性的数组)。这种限制是按设计的。

  • 自动装配不如显式布线精确。虽然如前面的表中所述,Spring谨慎地避免在可能产生意外结果的模糊性的情况下进行猜测。您不再明确记录Spring管理对象之间的关系。

  • 可能无法为可能从Spring容器生成文档的工具提供接线信息。

  • 容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组,集合或 Map实例,这不一定是个问题。但是,对于期望单个值的依赖关系,这种模糊性不是任意解决的。如果没有可用的唯一bean定义,则抛出异常。

在后一种情况下,您有几种选择:

  • 放弃自动装配以支持显式布线。

  • 通过将其autowire-candidate属性设置为bean,避免对bean定义进行自动装配false,如下一节所述。

  • 通过将primary其元素的属性设置为,将单个bean定义指定为主要候选者 true。

实现基于注释的配置可用的更细粒度控制,如基于注释的容器配置中所述。

从自动装配中排除Bean

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

该autowire-candidate属性旨在仅影响基于类型的自动装配。它不会影响名称的显式引用,即使指定的bean未标记为autowire候选,也会解析它。因此,如果名称匹配,则按名称自动装配会注入bean。

您还可以根据针对bean名称的模式匹配来限制autowire候选者。 顶级元素在其default-autowire-candidates属性中接受一个或多个模式。 例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供值* Repository。 要提供多个模式,请在逗号分隔的列表中定义它们。 bean定义的autowire-candidate属性的显式值true或false始终优先。 对于此类bean,模式匹配规则不适用。

这些技术对于您永远不希望通过自动装配注入其他bean的bean非常有用。这并不意味着使用自动装配本身不能配置排除的bean。相反,bean本身不是自动装配其他bean的候选者。

1.4.6 方法注入

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

解决方案是放弃一些控制反转。你可以做一个豆意识到容器通过实现ApplicationContextAware接口,并通过制作getBean(“B”)到容器调用请求(典型新)bean B实例的实例每次Bean A需要它。以下示例显示了此方法:

// 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;
    }
}

前面的内容是不可取的,因为业务代码知道并耦合到Spring Framework。方法注入是Spring IoC容器的一个高级功能,可以让您干净地处理这个用例。

查找方法注入

Lookup方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及原型bean,如上一节中描述的场景。Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入

  • 要使这个动态子类工作,Spring bean容器子类不能成为的类final,以及要重写的方法也不能final。

  • 对具有abstract方法的类进行单元测试需要您自己对类进行子类化并提供该abstract方法的存根实现。

  • 组件扫描也需要具体的方法,这需要具体的类来获取。

  • 另一个关键限制是查找方法不适用于工厂方法,特别是@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();
}

在包含要注入的方法的客户端类中(CommandManager在本例中),要注入的方法需要以下形式的签名:

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

如果方法是abstract,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。请考虑以下示例:

<!-- 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>

只要需要bean 的新实例,标识为bean的bean 就会commandManager调用自己的createCommand()方法myCommand。myCommand如果实际需要,您必须小心将bean 部署为原型。如果是单例,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();
}

或者,更具惯用性,您可以依赖于针对查找方法的声明返回类型解析目标bean:

public abstract class CommandManager {

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

    @Lookup
    protected abstract MyCommand createCommand();
}

请注意,您通常应该使用具体的存根实现来声明这种带注释的查找方法,以使它们与Spring的组件扫描规则兼容,其中默认情况下抽象类被忽略。此限制不适用于显式注册或显式导入的bean类。

访问不同范围的目标bean的另一种方法是ObjectFactory/ Provider注入点。将Scoped Beans视为依赖关系。

您也可能会发现ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config包中)有用。

任意方法替换

与查找方法注入相比,一种不太有用的方法注入形式是能够使用另一个方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您确实需要此功能。

使用基于XML的配置元数据,您可以使用该replaced-method元素将已存在的方法实现替换为已部署的bean。考虑以下类,它有一个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

因为参数的数量通常足以区分每个可能的选择,所以通过让您只键入与参数类型匹配的最短字符串,此快捷方式可以节省大量的输入。

猜你喜欢

转载自blog.csdn.net/hadues/article/details/86301629