SpringFramework Core(二)

2019-12-20  本文已影响0人  程序员文集

依赖关系

典型的企业应用程序不由单个对象(或Spring术语中的bean)组成。即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。下一部分将说明如何从定义多个独立的Bean定义到实现对象协作以实现目标的完全实现的应用程序。

依赖注入

依赖注入(DI)是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(即,与它们一起工作的其他对象),从工厂方法返回,然后,容器在创建bean时注入那些依赖项。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来控制bean自身依赖关系的实例化或位置的bean本身的逆过程(因此称为Control Inversion)。

使用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类不是通过继承关联的,则不存在潜在的歧义。因此,以下配置可以正常工作,并且您无需在<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>

当使用诸如的简单类型时 <value>true</value>,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的依赖注入

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

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

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

Spring团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在必需的依赖项null。此外,构造函数注入的组件始终以完全初始化的状态返回到客户端(调用)代码。附带说明一下,大量的构造函数自变量是一种不好的代码味道,这意味着该类可能承担了太多的职责,应该对其进行重构以更好地解决关注点分离问题。

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

依赖解析

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

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

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

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

作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够以String类型提供值转换成所有内置类型,比如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注入配置循环依赖关系。

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

通常,您可以信任Spring做正确的事。它在容器加载时检测配置问题,例如对不存在的Bean的引用和循环依赖项。在实际创建bean时,Spring设置属性并尽可能晚地解决依赖关系。这意味着,如果创建对象或其依赖项之一有问题,则正确加载的Spring容器以后可以在您请求对象时生成异常-例如,由于缺少或无效,Bean引发异常属性。某些配置问题的这种潜在的延迟可见性是为什么ApplicationContext默认情况下,实现会实例化单例bean。在实际需要这些bean之前,要花一些前期时间和内存来创建它们,您会在创建bean时发现配置问题ApplicationContext,而不是稍后。您仍然可以覆盖此默认行为,以便单例bean延迟初始化,而不是预先实例化。

如果不存在循环依赖关系,则当将一个或多个协作bean注入到依赖bean中时,每个协作bean在注入到依赖bean中之前都已完全配置。这意味着,如果bean A依赖于bean B,则Spring IoC容器会在对bean A调用setter方法之前完全配置beanB。换句话说,实例化了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。

延迟初始化

默认情况下,ApplicationContext实现会在初始化过程中积极创建和配置所有 单例 bean。通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周​​围环境中的错误。如果不希望出现这种情况,则可以通过将bean定义标记为延迟初始化来防止对singleton 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"/>

lazyBean是不是提前被实例化时,而not.lazy Bean急切预实例化。

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

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

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

自动装配协作器

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

自动装配可以大大减少指定属性或构造函数参数的需要。

随着对象的发展,自动装配可以更新配置。例如,如果您需要向类中添加依赖项,则无需修改配置即可自动满足该依赖项。

使用基于XML的配置元数据时(请参阅Dependency Injection),可以使用<bean/>元素的autowire属性为 bean定义指定自动装配模式。

四种自动装配模式:

no
默认)无自动装配。Bean引用必须由ref元素定义。对于较大的部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。

byName
按属性名称自动布线。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配并且包含一个master属性(即它具有一个 setMaster(..)方法),那么Spring将查找一个名为的bean定义,master并使用它来设置该属性。

byType
如果容器中恰好存在一个该属性类型的bean,则使该属性自动装配。如果存在多个错误,则会引发致命异常,这表明您不能byType对该bean 使用自动装配。如果没有匹配的bean,则什么都不会发生(未设置该属性)。

constructor
类似于byType但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个bean,则将引发致命错误。

自动装配的局限性和缺点

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

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

自动装配不如显式接线精确。尽管如前所述,Spring还是谨慎地避免猜测,以免产生意想不到的结果。Spring管理的对象之间的关系不再明确记录。

装配信息可能不适用于可能从Spring容器生成文档的工具。

容器内的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组,集合或 Map实例,这不一定是问题。但是,对于需要单个值的依赖项,不会任意解决此歧义。如果没有唯一的bean定义可用,则会引发异常。

从自动装配中排除Bean

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

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

上一篇下一篇

猜你喜欢

热点阅读