1.4 依赖(part1)

官方英文版地址:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html

备注:翻译如有不对,请多指正,谢谢。

1.4. Dependencies

A typical enterprise application does not consist of a single object (or bean in the Spring parlance). Even the simplest application has a few objects that work together to present what the end-user sees as a coherent application. This next section explains how you go from defining a number of bean definitions that stand alone to a fully realized application where objects collaborate to achieve a goal.

一个典型的企业应用程序一般包含不只一个对象。即使是最简单的应用也会含有若干个对象,这些对象一起工作给用户最终呈现一致的应用程序。下一节将解释如何从定义许多Bean定义到一个完全独立的对象相互协作共同完成一个目标的应用程序。

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 such, your classes become easier to test, in particular when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.

当对象具有依赖关系时,使用DI原则使得代码更加简洁,解耦更加有效。对象并不自己查找其依赖项,也不知道依赖项的位置或类型。因此,你的类变得更加容易测试,尤其当依赖关系在接口或者抽象类上,这允许存根或者Mock实现在单元测试中使用。

DI exists in two major variants, Constructor-based dependency injection and Setter-based dependency injection.

DI存在于两个主要的变体中,Constructor-based dependency injectionSetter-based dependency injection

Constructor-based dependency injection

基于构造函数的依赖注入

Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the bean is nearly equivalent, and this discussion treats arguments to a constructor and to a static factory method similarly. The following example shows a class that can only be dependency-injected with constructor injection. Notice that there is nothing special about this class, it is a POJO that has no dependencies on container specific interfaces, base classes or annotations.

基于构造函数的依赖注入是通过容器调用构造函数(构造函数可能包含若干参数,每个参数都代表一个依赖项)来实现的。通过调用具有特定参数的静态工厂方法构造对象也是类似的。这里的讨论将两种情况同等对待。接下来的例子展示了一个只能通过构造函数注入的类。需要注意的是,这个类并没有什么特别的地方,它是一个不依赖于容器特定接口、基础类和注解的POJO。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

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

Constructor argument resolution

构造函数参数解析

Constructor argument resolution matching occurs using the argument’s type. If no potential ambiguity exists in the constructor arguments of a bean definition, then the order in which the constructor arguments are defined in a bean definition is the order in which those arguments are supplied to the appropriate constructor when the bean is being instantiated. Consider the following class:

构造函数参数解析使用参数的类型匹配对应的参数。如果,在Bean定义的构造函数参数中不存在潜在的歧义,那么在Bean定义中构造函数参数定义的顺序就是这些参数提供给构造函数参数列表的顺序。看下下面的类定义:

package x.y;

public class Foo {

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

No potential ambiguity exists, assuming that Bar and Baz classes are not related by inheritance. Thus the following configuration works fine, and you do not need to specify the constructor argument indexes and/or types explicitly in the <constructor-arg/> element.

没有潜在的歧义存在,假设Bar和Biz类没有继承关系。那么,下面的配置可以工作的很好,你不需要在<constructor-arg/>元素中去显示指定构造参数的索引和类型。

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

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

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

When another bean is referenced, the type is known, and matching can occur (as was the case with the preceding example). When a simple type is used, such as <value>true</value>, Spring cannot determine the type of the value, and so cannot match by type without help. Consider the following class:

当引用另一个Bean时,类型是已知的,且可以自动匹配对应的类型。当使用简单类型时,例如<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;
    }
}

Constructor argument type matching

构造参数类型匹配

In the preceding scenario, the container can use type matching with simple types if you explicitly specify the type of the constructor argument using the type attribute. For example:

在前面的例子中,如果使用type属性显示指定构造参数的类型,容器对于简单类型可以进行类型匹配。举个例子:

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

Constructor argument index

构造参数索引

Use the index attribute to specify explicitly the index of constructor arguments. For example:

使用index属性来显示指定构造参数的索引下标,举个例子:

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

In addition to resolving the ambiguity of multiple simple values, specifying an index resolves ambiguity where a constructor has two arguments of the same type. Note that the index is 0 based.

除了解决多个简单类型值的模糊性外,指定索引也能够解决构造函数中含有两个相同类型参数时的模糊性问题。需要注意的是,索引从0开始。

Constructor argument name

使用构造参数名

You can also use the constructor parameter name for value disambiguation:

你可以使用构造参数名来消除歧义。

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

Keep in mind that to make this work out of the box your code must be compiled with the debug flag enabled so that Spring can look up the parameter name from the constructor. If you can’t compile your code with debug flag (or don’t want to) you can use @ConstructorProperties JDK annotation to explicitly name your constructor arguments. The sample class would then have to look as follows:

请注意,要使这个功能发挥作用,你的代码必须在开启调试标识的情况下进行编译,以便Spring可以在构造函数中查找参数名称。如果不能使用调试标识(或者不想使用),你可以使用@ConstructorProperties注解来显示指定构造参数的名称。示例类如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

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

Setter-based dependency injection

基于Setter方法的依赖注入

Setter-based DI is accomplished by the container calling setter methods on your beans after invoking a no-argument constructor or no-argument static factory method to instantiate your bean.

基于Setter方法的依赖注入是通过容器在调用一个无参构造函数或者无参静态工厂方法后调用Bean的Setter方法来实现的。

The following example shows a class that can only be dependency-injected using pure setter injection. This class is conventional Java. It is a POJO that has no dependencies on container specific interfaces, base classes or annotations.

下面的例子显示了一个只能通过纯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...
}

The ApplicationContext supports constructor-based and setter-based DI for the beans it manages. It also supports setter-based DI after some dependencies have already been injected through the constructor approach. You configure the dependencies in the form of a BeanDefinition, which you use in conjunction with PropertyEditor instances to convert properties from one format to another. However, most Spring users do not work with these classes directly (i.e., programmatically) but rather with XML bean definitions, annotated components (i.e., classes annotated with @Component, @Controller, etc.), or @Bean methods in Java-based @Configuration classes. These sources are then converted internally into instances of BeanDefinition and used to load an entire Spring IoC container instance.

ApplicationContext支持为其管理的Bean提供基于构造函数和setter方法的依赖注入。同时,也支持在通过构造函数注入部分依赖后使用setter方法注入其他属性。你可以以BeanDefinition的形式配置依赖关系,并将其与PropertyEditor一起使用来将一个格式转换为另一种格式。然而,大多数Spring用户并不会直接使用这些类,而是使用基于XML的Bean定义,或是使用@Component、@Controller等注解的组件,亦或在基于@Configuration注解的类中使用@Bean。这些各种形式的配置源将会在内部转换为BeanDefinition,并用于加载整个Spring容器。

Dependency resolution process

依赖解析过程

The container performs bean dependency resolution as follows:

  • The ApplicationContext is created and initialized with configuration metadata that describes all the beans. Configuration metadata can be specified via XML, Java code, or annotations.

  • For each bean, its dependencies are expressed in the form of properties, constructor arguments, or arguments to the static-factory method if you are using that instead of a normal constructor. These dependencies are provided to the bean, when the bean is actually created.

  • Each property or constructor argument is an actual definition of the value to set, or a reference to another bean in the container.

  • Each property or constructor argument which is a value is converted from its specified format to the actual type of that property or constructor argument. By default Spring can convert a value supplied in string format to all built-in types, such as int, long, String, boolean, etc.

容器执行一下bean依赖项解析:

  • ApplicationContext由描述所有Bean的配置元数据来创建和初始化。配置元数据可以通过XML、Java代码或者注解来指定;
  • 对于每个Bean,它的依赖项通过属性、构造参数、静态工厂方法的参数等形式描述,这些依赖在bean实际被创建的时候会提供给bean;
  • 每个属性或构造参数是要设置值的实际定义,或者指向容器内其他bean对象的引用;
  • 每个属性或构造参数都被被转换成其实际的类型,默认情况下,Spring可以将以字符串提供的值转换成任何内置的类型,如int,long,String或者boolean类型等;

The Spring container validates the configuration of each bean as the container is created. However, the bean properties themselves are not set until the bean is actually created. Beans that are singleton-scoped and set to be pre-instantiated (the default) are created when the container is created. Scopes are defined in Bean scopes. Otherwise, the bean is created only when it is requested. Creation of a bean potentially causes a graph of beans to be created, as the bean’s dependencies and its dependencies' dependencies (and so on) are created and assigned. Note that resolution mismatches among those dependencies may show up late, i.e. on first creation of the affected bean.

Spring容器在容器创建的时候会校验每个bean定义配置的合法性。但是,bean的属性只有等到bean被创建的时候才会去设置。那些单例的Bean后者设置预初始化的Bean在容器创建的时候会被创建。作用域在 Bean scopes中进行了定义。否则,bean对象只在被请求的时候才会被创建。一个bean的创建潜在的可能引起一些列bean会被创建,因为bean之间的依赖以及他们依赖所对应的依赖(等等)。需要注意的是,那些依赖项之间的解析不匹配可能出现的较晚,比如在受影响bean第一次创建的时候。

You can generally trust Spring to do the right thing. It detects configuration problems, such as references to non-existent beans and circular dependencies, at container load-time. Spring sets properties and resolves dependencies as late as possible, when the bean is actually created. This means that a Spring container which has loaded correctly can later generate an exception when you request an object if there is a problem creating that object or one of its dependencies. For example, the bean throws an exception as a result of a missing or invalid property. This potentially delayed visibility of some configuration issues is why ApplicationContext implementations by default pre-instantiate singleton beans. At the cost of some upfront time and memory to create these beans before they are actually needed, you discover configuration issues when the ApplicationContext is created, not later. You can still override this default behavior so that singleton beans will lazy-initialize, rather than be pre-instantiated.

你通常可以相信Spring会做正确的事情。在容器加载的时候,它会检测配置问题,例如引用并不存在的bean和循环依赖。当bean被实际创建的时候,Spring会尽可能晚的设置属性和解析依赖关系。这意味着,一个正确加载的Spring容器在你请求一个对象的时候可能产生一个异常,如果在创建对象后者它的依赖项的时候出现异常的话。例如,bean会因为丢失或者无效属性抛出异常。这种潜在的对一些配置问题的延迟可见性,这是ApplicationContext实现默认情况下预初始化单例bean的原因。在它们实际被创建的时候,需要花费一些时间和内存来创建它们,在ApplicationContext创建的时候你会发现配置问题。你仍然可以覆盖这个默认行为使得单例bean可以延迟初始化,而不是预初始化。

If no circular dependencies exist, when one or more collaborating beans are being injected into a dependent bean, each collaborating bean is totally configured prior to being injected into the dependent bean. This means that if bean A has a dependency on bean B, the Spring IoC container completely configures bean B prior to invoking the setter method on bean A. In other words, the bean is instantiated (if not a pre-instantiated singleton), its dependencies are set, and the relevant lifecycle methods (such as a configured init method or the InitializingBean callback method) are invoked.

如果没有循环依赖存在,当一个或多个协作bean被注入到一个独立的bean时,每个协作bean都会在注入到独立的bean之前预先完全配置好。这意味着,如果bean A依赖bean B, Spring容器在调用bean A的setter方法进行注入之前会完全配置好bean B。换句话说,bean被初始化,它的依赖关系被设置,相关的生命周期方法(如配置初始化方法configured init method 或者回调方法InitializingBean callback method)被调用。

Examples of dependency injection

依赖注入的例子

The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions:

接下来的例子使用基于XML的配置元数据进行setter方法注入。下面是指定一些bean定义的一小部分XML配置:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

In the preceding example, setters are declared to match against the properties specified in the XML file. The following example uses constructor-based DI:

在前面的例子中,setter方法被定义用来和XML文件中定义的属性进行匹配。下面的例子使用构造函数注入:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

The constructor arguments specified in the bean definition will be used as arguments to the constructor of the ExampleBean.

在bean定义中的构造函数参数将会被作为构造函数ExampleBean的参数。

Now consider a variant of this example, where instead of using a constructor, Spring is told to call a static factory method to return an instance of the object:

现在考虑这个例子的一个变体,不再使用构造函数,告诉Spring调用静态工厂方法返回一个对象的实例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

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

Arguments to the static factory method are supplied via <constructor-arg/> elements, exactly the same as if a constructor had actually been used. The type of the class being returned by the factory method does not have to be of the same type as the class that contains the static factory method, although in this example it is. An instance (non-static) factory method would be used in an essentially identical fashion (aside from the use of the factory-bean attribute instead of the class attribute), so details will not be discussed here.

静态工厂方法的参数通过<constructor-arg>元素提供,就像构造函数实际被使用一样。静态工厂方法返回的类型并不一定要和包含静态工厂方法的类的类型相同,尽管在这个例子中是这样的。实例工厂方法以类似相同的方式进行使用(使用factory-bean属性,而不是class属性),所以在这里不讨论具体细节。

猜你喜欢

转载自blog.csdn.net/andamajing/article/details/81637823
1.4