Spring5中文文档【4】- IOC容器之依赖关系

1. 前言

本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址
内容较杂,建议粗略过一遍,熟悉下概念即可,后续结合实际开发详解。

典型的企业应用程序肯定不止包含一个对象(或Spring 用语中的 bean)。即使是最简单的应用程序也有一些对象,它们协同工作以呈现最终用户所看到的应用程序。下一节将解释如何从定义多个独立的 bean到完全实现的应用程序,其中对象如何协作以实现目标。

1.1 依赖注入(DI)

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

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

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

1.1.1 基于构造函数的依赖注入

基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数代表一个依赖项。调用static带有特定参数的工厂方法来构造 bean 几乎是等效的,本讨论将static类似地处理构造函数和工厂方法的参数。

以下示例显示了UserInfo类依赖一个Dept属性,通过构造函数注入:

@Data
public class UserInfo {
   
     

    public UserInfo() {
   
     
    }

    String username;

    Integer pwd;

    Dept dept;

    public UserInfo(Dept dept) {
   
     
        this.dept = dept;
    }
}

接着在XML中,我们可以通过<constructor-arg />标签,让框架对UserInfo Bean对象注入相关的依赖(属性赋值)。

    <!--基于构造函数的依赖注入-->
    <bean id="userInfo" class="org.pearl.spring.demo.pojo.UserInfo">
        <constructor-arg ref="dept"></constructor-arg>
    </bean>
    <bean id="dept" class="org.pearl.spring.demo.pojo.Dept">
    </bean>

<constructor-arg />标签说明:

  • 一个<constructor-arg />元素表示构造方法的一个参数,且使用时不区分顺序。
  • 通过<constructor-arg>元素的index属性可以指定该参数的位置索引,位置从0开始。
  • <constructor-arg>元素还提供了type属性用来指定参数的类型,避免字符串和基本数据类型的混淆。

构造函数参数解析

构造函数参数解析通过使用参数的类型匹配。如果 bean 定义的构造函数参数中不存在潜在的歧义,那么在构建bean时构造函数参数的顺序就是将这些参数提供给适当的构造函数的顺序。

比如以下类:

package x.y;

public class ThingOne {
   
     

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

假设ThingTwo和ThingThree类不通过继承相关,则不存在潜在的歧义。因此,以下配置工作正常,不需要在<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 可以从构造函数中查找参数名称。如果您不能或不想使用调试标志编译代码,则可以使用JDK 注解 @ConstructorProperties 显式命名构造函数参数。示例类必须如下所示:

package examples;

public class ExampleBean {
   
     

    // Fields omitted

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

总结

如果为Bean对象注入依赖时,通过构造参数输入时,需要有参构造,还需要各种声明,灵活性差,仅靠重载限制太多。一般实际不会用这么方式。

1.1.2 基于 Setter 的依赖注入

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

以下示例显示了一个只能使用纯 setter 注入进行依赖注入的类,在依赖注入时,会调用Setter方法进行注入。

    <!--基于Setter的依赖注入-->
    <!--Ref-->
<bean id="userInfo" class="org.pearl.spring.demo.pojo.UserInfo">
            <property name="dept" ref="dept"/>
            <property name="username" value="San"/>
</bean>
<bean id="dept" class="org.pearl.spring.demo.pojo.Dept"> </bean>

ApplicationContext支持基于构造函数和Setter的 DI注入其管理的Bean对象。

但是,大多数 Spring 用户不直接(即以编程方式)使用这些类,而是使用 XML bean 定义、带注解的组件(即用@Component、 @Controller等注解的类)或基于 Java 的@Configuration类中@Bean的方法。这些源在内部转换为实例Bean Definition并用于加载整个 Spring IoC 容器实例。

1.1.3 依赖解析过程

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

使用ApplicationContext对所有 bean 的配置元数据进行创建和初始化。配置元数据可以由 XML、Java 代码或注解指定。

对于每个 bean,它的依赖关系以属性、构造函数参数或静态工厂方法的参数(如果您使用它而不是普通构造函数)的形式表示。在实际创建 bean 时,将这些依赖关系提供给 bean。

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

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

Spring 容器在创建容器时验证每个 bean 的配置。但是,在实际创建 bean 之前不会设置 bean 属性本身。创建容器时会创建单例范围并设置为预实例化(默认)的 Bean。范围在Bean Scopes中定义。否则,仅在请求时才创建 bean。创建 bean 可能会导致创建 bean 图,因为 bean 的依赖项及其依赖项的依赖项(等等)被创建和分配。请注意,这些依赖项之间的解析不匹配可能会出现较晚,即在第一次创建受影响的 bean 时。

1.1.4 循环依赖

什么是循环依赖?

顾名思义,循环依赖就是A依赖B,B又依赖A,两者之间的依赖关系形成了一个圆环,通常是由于不正确的编码所导致。

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

如何解决?

一种可能的解决方案是编辑一些类的源代码,以便由 setter 而不是构造函数来配置。或者,避免构造函数注入并仅使用 setter 注入。也就是说,虽然不推荐,但是可以通过setter注入来配置循环依赖。

与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖迫使其中一个 bean 在完全初始化之前注入另一个 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工厂方法的参数由<constructor-arg/>元素提供,就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含static工厂方法的类的类型相同(尽管在本示例中是)。

1.4.2. 详细依赖和配置

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

在value所述的属性<property/>元素指定属性或构造器参数的人类可读的字符串表示。Spring 的 转换服务用于将这些值从 a 转换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="misterkaoli"/>
</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
    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>

前面的XML 更简洁。但是,拼写错误是在运行时而不是设计时发现的,除非您在创建 bean 定义时使用支持自动属性完成的 IDE(例如IntelliJ IDEA或Spring Tools for Eclipse)。

您还可以配置一个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>

Spring 容器通过使用 JavaBeans机制将<value/>元素内部的文本转换为 java.util.Properties实例PropertyEditor。这是一个很好的捷径,并且是 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>

第一种形式比第二种形式更可取,因为使用idref标记可以让容器在部署时验证引用的命名 bean 是否实际存在。在第二个变体中,不对传递给bean target Name属性的值执行验证client。只有在client实际实例化 bean时才会发现拼写错误(最有可能是致命的结果)。如果client bean 是原型bean,则可能只有在部署容器很久之后才能发现此错误和由此产生的异常。

对其他 Bean 的引用

ref元素是在<constructor-arg/><property/> 标签中定义。在这里,您将 bean 的指定属性的值设置为对容器管理的另一个 bean(协作者)的引用。被引用的 bean 是要设置其属性的 bean 的依赖项,在设置属性之前根据需要对其进行初始化。(如果协作者是一个单例 bean,它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。范围和验证取决于您是否通过beanorparent属性指定其他对象的 ID 或名称。

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

<ref bean="someBean"/>

通过parent属性指定目标 bean会创建对当前容器的父容器中的 bean 的引用。parent 属性的值可以id与目标 bean的属性或目标 bean 属性中的值之一相同name。目标 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>

在local上属性ref元素在4.0豆XSD不再支持,因为它没有提供比普通值bean参考了。升级到 4.0 架构时更改现有ref local引用ref bean。

<bean/>内部的元件<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 的作用域。

<list/><set/><map/>,和<props/>元件设置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>

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

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

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

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

以下示例演示了集合合并:

<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集合的结果。以下清单显示了结果:

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

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

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

集合合并的限制

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

强类型集合

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

Null 和空字符串值

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 模式定义。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 格式则不然。我们建议您谨慎选择您的方法并将其传达给您的团队成员,以避免同时使用所有三种方法生成 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 属性名称不能以数字开头(即使某些 IDE 允许)。相应的索引符号也可用于<constructor-arg>元素但不常用,因为在那里声明的简单顺序通常就足够了。

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

复合属性名称

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

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

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

1.4.3. 使用depends-on

如果一个 bean 是另一个 bean 的依赖项,这通常意味着一个 bean 被设置为另一个 bean 的属性。通常,您使用基于 XML 的配置元数据中的<ref/>元素来完成此操作。但是,有时 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/>,如以下示例所示:

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

当前面的配置被 使用时ApplicationContext,lazybean 在ApplicationContext启动时不会被预先实例化,而not.lazybean 会被预先实例化。

但是,当延迟初始化 bean 是未延迟初始化的单例 bean 的依赖项时,它ApplicationContext会在启动时创建延迟初始化 bean,因为它必须满足单例的依赖项。延迟初始化的 bean 被注入到其他地方没有延迟初始化的单例 bean 中。

您还可以通过使用元素default-lazy-init上的属性来控制容器级别的延迟初始化 <beans/>,如以下示例所示:

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

1.4.5. 自动装配

Spring 容器可以自动装配协作 bean 之间的关系。您可以让 Spring 通过检查ApplicationContext. 自动装配具有以下优点:

  • 自动装配可以显着减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,例如 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或constructor自动装配模式,您可以连接数组和类型化集合。在这种情况下,提供容器内与预期类型匹配的所有自动装配候选者以满足依赖关系。Map如果预期的键类型是 ,您可以自动装配强类型实例String。自动装配Map 实例的值由与预期类型匹配的所有 bean 实例组成,并且 Map实例的键包含相应的 bean 名称。

自动装配的局限性和缺点

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

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

  • property和constructor-arg设置中的显式依赖项始终覆盖自动装配。您不能自动装配简单属性,例如基元 Strings、 和Classes(以及此类简单属性的数组)。此限制是有意设计的。
  • 自动装配不如显式装配精确。虽然,如前面的表中所述,Spring 小心避免在可能产生意外结果的歧义的情况下进行猜测。不再明确记录 Spring 管理的对象之间的关系。
  • 可能无法从 Spring 容器生成文档的工具中使用接线信息。
  • 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型相匹配。对于数组、集合或 Map实例,这不一定是问题。但是,对于期望单个值的依赖项,这种歧义不会被任意解决。如果没有唯一的 bean 定义可用,则抛出异常。

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

  • 放弃自动装配以支持显式装配。
  • 如下一节所述,通过将其autowire-candidate属性设置为 来避免对 bean 定义进行自动装配。false
  • 通过将primary其<bean/>元素的属性设置为 ,将单个 bean 定义指定为主要候选者 true。
  • 使用基于注解的配置实现更细粒度的控制,如基于注解的容器配置 中所述。

从自动装配中排除 Bean

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

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

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

这些技术对于您永远不想通过自动装配注入其他 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 的新实例。

一个解决方案是放弃一些控制反转。您可以通过实现接口来使 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 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在本例中为 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>

标识为的 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) {
   
     
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}

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

访问不同范围的目标 bean 的另一种方法是ObjectFactory/ Provider注入点。参见Scoped Beans as Dependencies。

您可能还会发现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

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

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: