Java教程

Spring文档之依赖关系

本文主要是介绍Spring文档之依赖关系,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录
  • 基于构造函数的依赖注入
    • 构造函数参数解析
  • 基于 Setter 的依赖注入
  • 依赖解析过程
  • 循环依赖
  • 依赖注入的例子

依赖注入 (DI) 是一个过程,其中对象定义它们的依赖(它们的方法所依赖的其他对象)仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回。然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。

Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes or the Service Locator pattern.

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

Code is cleaner with the DI principle, and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies and does not know the location or class of the dependencies. As a result, your classes become easier to test, particularly when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.

基于构造函数的依赖注入

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

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final 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) {
        // ...
    }
}

假设ThingTwoThingThree类不通过继承相关,则不存在潜在的歧义。因此,以下配置工作正常,您不需要在<constructor-arg/>元素中显式指定构造函数参数索引或类型 。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

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

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

当另一个 bean 被引用时,类型是已知的,并且可以发生匹配(就像前面的例子一样)。当使用简单类型时,例如<value>true</value>,Spring 无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。考虑以下类:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final 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 可以从构造函数中查找参数名称。如果您不能或不想使用调试标志编译代码,则可以使用 @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 的 DI 是通过容器在调用无参数构造函数或无参数static工厂方法来实例化bean 之后调用 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支持其管理的bean的基于构造函数和基于setter的依赖注入。在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以以BeanDefinition的形式配置依赖项,并将其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。
然而,大多数Spring用户并不直接使用这些类(即编程方式),而是使用XML bean定义、带注释的组件(即使用@Component、@Controller等注释的类)或基于java的@Configuration类中的@Bean方法。然后这些源在内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。

基于构造函数还是基于 setter 的 DI?

由于您可以混合使用基于构造函数和基于 setter 的 DI,因此根据经验,对强制依赖项使用构造函数,对可选依赖项使用 setter 方法或配置方法是一个很好的经验法则。请注意, 在 setter 方法上使用@Required注释可用于使属性成为必需的依赖项;但是,最好使用带有参数编程验证的构造函数注入。

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

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

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

依赖解析过程

容器执行bean依赖解析如下:

  • 使用ApplicationContext描述所有 bean 的配置元数据创建和初始化。配置元数据可以由 XML、Java 代码或注释指定。
  • 对于每个 bean,它的依赖关系以属性、构造函数参数或静态工厂方法的参数(如果您使用它而不是普通构造函数)的形式表示。在实际创建 bean 时,将这些依赖关系提供给 bean。
  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 bean 的引用。
  • 作为值的每个属性或构造函数参数都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如intlongStringboolean等。

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 的引用和循环依赖。Spring 在真正创建 bean 时尽可能晚地设置属性并解析依赖项。这意味着,如果创建该对象或其依赖项时出现问题,则已正确加载的 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。

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

使用基于构造函数的 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工厂方法的参数由<constructor-arg/>元素提供,就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含static工厂方法的类的类型相同(尽管在本示例中是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用factory-bean属性而不是class属性)。

2.4.2 详细的依赖和配置

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

<property/>元素指定属性或构造器参数的value属性的值作为一个人类可读的字符串表示。Spring 的 转换服务用于将这些值从一个字符串转换为属性或参数的实际类型。

<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="misterkaoli"/>
</bean>

使用p-namespace进行更简洁的 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
    https://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="misterkaoli"/>

</beans>

配置一个java.util.Properties实例

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

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

pring 容器通过使用 JavaBeansPropertyEditor机制将<value/>元素内部的文本转换为 java.util.Properties实例。这是一个很好的捷径,并且是 Spring 团队支持使用嵌套<value/>元素而不是value属性样式的少数几个地方之一。

idref元素

idref元素是一个简单的防错方法对通过id(一个字符串值-而不是参考)在该容器另一个bean的一个<constructor-arg/><property/> 元素。

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

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

前面的 bean 定义片段与以下片段完全等效(在运行时):

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

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>
对其他 Bean 的引用(合作者)

' ref '元素是' '或' '定义元素中的最后一个元素。在这里,您将bean的指定属性的值设置为容器管理的另一个bean(合作者)的引用。被引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要初始化它。(如果合作者是一个单例bean,它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是通过“bean”属性还是“parent”属性指定另一个对象的ID或名称。

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

<ref bean="someBean"/>

通过' parent '属性指定目标bean将创建对当前容器的父容器中的bean的引用。“parent”属性的值可以与目标bean的“id”属性或目标bean的“name”属性中的一个值相同。目标bean必须位于当前bean的父容器中。当您有容器的层次结构,并且希望使用与父bean同名的代理将现有bean包装在父容器中时,您应该主要使用此bean引用变体。
下面的两个清单展示了如何使用' parent '属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required 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>
Inner Beans

property/><constructor-arg/>元素中的元素定义一个内部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>

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

作为一个极端情况,可以从自定义范围接收销毁回调——例如,对于包含在单例 bean 中的请求范围内的 bean。内部 bean 实例的创建与其包含的 bean 相关联,但销毁回调让它参与请求范围的生命周期。这不是一个常见的场景。内部 bean 通常只是共享它们包含的 bean 的作用域。

Collections

<list/><set/><map/>,和<props/>元素分别地设置JavaCollection类型ListSetMap,和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>

The value of a map key or value, or a set value, can also be any of the following elements:

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 定义的merge=true属性的<props/>元素上使用 adminEmails属性child。当child容器解析并实例化 bean 时,生成的实例有一个adminEmails Properties集合,其中包含合并子集合 adminEmails与父adminEmails集合的结果。

集合合并的限制

您不能合并不同的集合类型(例如 MapList)。如果您确实尝试这样做,Exception则会抛出适当的。merge必须在较低的继承子定义上指定该属性。merge在父集合定义上指定属性是多余的,不会导致所需的合并。

强类型集合

随着 Java 5 中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。如果您使用 Spring 将强类型依赖注入Collection到 bean 中,则可以利用 Spring 的类型转换支持,以便在将强类型Collection 实例的元素添加到Collection.

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>可以通过反射获得。因此,弹簧的类型转换基础设施识别的各种值的元素为类型的Float,并且字符串值(9.992.75,和 3.99)被转换成实际的Float类型。

Null 和空字符串值

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

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

前面的示例等效于以下 Java 代码:

exampleBean.setEmail("");

<null/>元素处理null值。以下清单显示了一个示例:

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

上面的配置相当于下面的Java代码:

exampleBean.setEmail(null);
带有 p 命名空间的 XML 快捷方式

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

Spring 支持具有命名空间的可扩展配置格式,这些格式基于 XML 模式定义。beans本章讨论的配置格式是在 XML Schema 文档中定义的。但是,p 命名空间并未在 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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

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

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</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
        https://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 定义用于<property name="spouse" ref="jane"/>创建从 beanjohn到 bean的引用 jane,而第二个 bean 定义p:spouse-ref="jane"用作属性来执行完全相同的操作。在这种情况下,spouse是属性名称,而-ref部分表示这不是一个直接值,而是对另一个 bean 的引用。

p 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与以 结尾的属性冲突Ref,而标准 XML 格式则不然。

带有 c 命名空间的 XML 快捷方式

与带有 p-namespace的XML Shortcut类似,Spring 3.1 中引入的 c-namespace 允许内联属性来配置构造函数参数而不是嵌套constructor-arg元素。

下面的示例使用c:命名空间来做与 from Constructor-based Dependency Injection相同的事情 :

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

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

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

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>

由于 XML 语法,索引表示法需要存在前导_,因为 XML 属性名称不能以数字开头

复合属性名称

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

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

所述something豆具有fred属性,该属性具有bob属性,其具有sammy 特性,并且最终sammy属性被设置为值123。为了使其工作,fred属性 ofsomethingbob属性fred不能null在 bean 被构造之后。否则,NullPointerException抛出

2.4.3使用depends-on

如果一个bean是另一个bean的依赖项,这通常意味着一个bean被设置为另一个bean的属性。
通常使用基于xml的配置元数据中的元素来实现这一点。然而,有时bean之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化式时,例如数据库驱动程序注册。依赖属性可以显式强制在初始化使用此元素的bean之前初始化一个或多个bean。
下面的例子使用依赖属性来表示对单个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也可以控制关机顺序。

2.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使用时,惰性bean不会在ApplicationContext启动时立即预实例化,而不是惰性bean是主动预实例化的。然而,当一个延迟初始化的bean是一个没有延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建这个延迟初始化的bean,因为它必须满足单例bean的依赖项。延迟初始化的bean被注入到没有延迟初始化的其他地方的单例bean中。

您还可以在容器级使用元素上的default-lazy-init属性来控制延迟初始化,如下面的示例所示:

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

2.4.5. 自动装配合作者

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

  • 自动装配可以显著减少指定属性或构造函数参数的需要。
    (本章其他部分讨论的其他机制,如bean模板,在这方面也很有价值。)
  • 自动装配可以随着对象的发展更新配置。
    例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。
    因此,自动连接在开发过程中特别有用,当代码库变得更加稳定时,无需放弃切换到显式连接的选项。

在使用基于xml的配置元数据时(请参阅依赖项注入),您可以使用元素的autowire属性为bean定义指定自动连接模式。自动装配功能有四种模式。您可以指定每个bean的自动装配,从而可以选择要自动装配哪些bean。自动装配的四种模式如下表所示:

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

使用byType或构造函数自动装配模式,可以连接数组和类型化集合。在这种情况下,容器中所有匹配预期类型的自动装配候选对象都将被提供以满足依赖关系。如果期望的键类型是String,则可以自动装配强类型Map实例。自动连接的Map实例的值包含所有与期望类型匹配的bean实例,Map实例的键包含相应的bean名称。

自动装配的局限性和缺点

当在整个项目中始终如一地使用自动装配时,它的工作效果最好。
如果自动装配通常不使用,那么只使用它来连接一个或两个bean定义可能会让开发人员感到困惑。

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

  • 属性和构造参数设置中的显式依赖总是覆盖自动装配。
    不能自动连接简单属性,如原语、字符串和类(以及这些简单属性的数组)。
    这种限制是由设计造成的。

  • 自动装配没有显式装配精确。
    不过,正如前面的表中所指出的,Spring小心地避免猜测,以防出现可能产生意外结果的歧义。
    spring管理对象之间的关系不再显式地记录下来。

  • 连接信息可能对可能从Spring容器生成文档的工具不可用。

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

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

放弃自动装配,选择显式装配。

  • 通过将bean的autowire候选属性设置为false,可以避免bean定义的自动装配,如下一节所述。

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

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

从自动装配中排除 Bean

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

注意:autowire-candidate属性被设计为仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的bean没有被标记为自动连接候选对象,也会解析该引用。因此,如果名称匹配,按名称自动装配仍然会注入一个bean。

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

这些技术对于那些永远不想通过自动装配将其注入到其他bean中的bean非常有用。这并不意味着被排除的bean本身不能通过使用自动装配来配置。相反,bean本身不是自动装配其他bean的候选对象。

2.4.6. 方法注入

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

一个解决方案是放弃一些控制反转。您可以通过实现接口来使 bean A 了解容器ApplicationContextAware,并通过在getBean("B")每次 bean A 需要时调用容器来请求(通常是新的)bean 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;
    }
}

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

查找方法注入

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

注意

  • 要使这个动态子类化工作,Spring bean容器子类的类不能是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();
}

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

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

标识为commandManager的bean在需要myCommand bean的新实例时调用它自己的createCommand()方法如果实际需要将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) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}

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

任意方法替换

方法注入的一种不如查找方法注入有用的形式是,能够用另一种方法实现替换托管bean中的任意方法。在真正需要此功能之前,您可以安全地跳过本节的其余部分。

对于基于xml的配置元数据,可以使用replacement -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"/>

您可以<arg-type/>在元素内使用一个或多个元素<replaced-method/> 来指示被覆盖的方法的方法签名。仅当方法重载并且类中存在多个变体时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配 java.lang.String

java.lang.String
String
Str

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

这篇关于Spring文档之依赖关系的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!