典型的企业应用程序不是由单个对象(或Spring说明中的bean)组成的。即使是最简单的应用程序,也有几个对象共同合作,展示最终用户看到的一致性应用程序。下一节将介绍如何从一些独立于完全实现的应用程序的bean定义(对象协作实现目标)。
7.4.1 Dependency Injection(依赖注入)
依赖注入(DI)是对象定义其依赖性的过程,也就是他们工作的其他对象,只有通过构造函数参数,工厂方法的参数,或者在从工厂方法构造或返回后在对象实例上设置的属性。然后,容器在创建bean时注入这些依赖项。这个过程基本上是逆向的,因此,控制反转(IoC)名称本身就是通过使用直接构造类或者服务定位器模式来控制自己的依赖关系的实例化或位置。
使用DI原理,代码更清晰,当对象具有依赖关系时,解耦更有效。对象不查找其依赖关系,并且不知道依赖关系的位置或类。因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,允许在单元测试中使用存根 (stub)或模拟 (mock)实现。
DI存在两种主要的变体:基于构造函数的依赖注入(Constructor-based dependency injection)和基于Setter的依赖注入(Setter-based dependency injection)。
基于构造函数的依赖注入( Constructor-based dependency injection )
基于构造函数的DI通过容器调用具有多个参数的构造函数完成,每个参数表示一个依赖关系。调用一个具有特定参数的静态工厂方法来构造bean几乎是等效的,这个讨论类似地将参数视为一个构造函数和一个静态的工厂方法。
以下示例显示一个只能使用构造函数注入的依赖注入的类。请注意,这个类没有什么特别之处,它是一个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... }
构造函数解析
使用参数的类型进行构造函数参数解析匹配.如果在bean定义的构造函数参数中不存在潜在的歧义,那么构造函数参数在bean定义中定义的顺序就是在bean被实例化时将这些参数提供给适当的构造函数的顺序。考虑下面的类:
package x.y; public class Foo { public Foo(Bar bar,Baz baz) { // ... } }
没有潜在的歧义
<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>
,假设Bar和Baz类与继承无关。因此,以下配置工作正常,您不需要在<constructor-arg />元素中显式指定构造函数参数索引和/或类型。
当引用另一个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; } }
在上述情况下,如果使用type属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的类型。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
<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-based dependency injection)
基于Setter的DI通过在调用无参数构造函数或无参数静态工厂方法来实例化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容器实例。
Constructor-based or setter-based DI? 由于可以混合基于构造函数和基于setter的DI,因此使用构造函数来强制依赖关系和setter方法或可选依赖关系的配置方法是一个很好的经验法则。请注意,可以使用setter方法上的@required注释来使属性成为必需的依赖关系。 Spring团队通常主张构造函数注入,因为它可以将应用程序组件实现为不可变对象,并确保所需的依赖关系不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为一个附注,大量的构造函数论证是一个坏的代码气味,这意味着该类可能有太多的责任,应该重构以更好地解决问题的正确分离。 Setter注入应主要用于可选依赖关系,可以在类中分配合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是setter方法使得该类的对象可以在以后重新配置或重新注入。因此,通过JMX管理MBean是一种引人注目的用例。 有时候,当您处理没有来源的第三方class时,您可以选择。例如,如果第三方类不暴露任何setter方法,那么构造函数注入可能是DI的唯一可用形式。 |
Dependency resolution process
容器执行bean依赖解析如下:
使用描述所有bean的配置元数据创建和初始化ApplicationContext。可以通过XML,Java代码或注释指定配置元数据。
对于每个bean,如果使用它,而不是正常的构造函数,其依赖性以静态工厂方法的属性,构造函数参数或参数的形式表示。当bean实际创建时,这些依赖关系被提供给bean。
每个属性或构造函数参数是要设置的值的实际定义,或对容器中另一个bean的引用。
作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。
创建容器时,Spring容器会验证每个bean的配置。但是,bean实际创建之前不会设置bean属性。单个范围(singleton-scoped)并设置为预实例化 (pre-instantiated)(默认)的Bean将在创建容器时创建。Scopes are defined inSection7.5,“Bean scopes”.否则,只有在请求时才创建该bean。创建bean可能会导致创建一系列bean,因为创建和分配了bean的依赖关系及其依赖关系(等等)。请注意,这些依赖关系中的解决方案不匹配可能会显示较晚,即首次创建受影响的bean。
循环依赖(circular dependencies)
如果您主要使用构造函数注入,则可以创建一个无法解决的循环依赖性场景。
例如:Class 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的一些前期时间和内存为代价,您可以在创建ApplicationContext时发现配置问题,而不是以后。您仍然可以覆盖此默认行为,以便单例bean将进行延迟初始化,而不是预先实例化。
如果不存在循环依赖关系,当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入依赖bean之前被完全配置。这意味着如果bean A对bean B有依赖关系,则Spring IoC容器在调用bean A的setter方法之前完全配置bean B。换句话说,bean被实例化(如果不是预先实例化的单例),则设置它的依赖关系,并调用相关的生命周期方法(如配置的init方法或InitializingBean回调方法)。
Examples of dependency injection
以下示例使用基于设置器的DI的基于XML的配置元数据。 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"/>
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"/>
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被要求调用一个静态工厂方法来返回对象的一个实例:
<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,int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }
静态工厂方法的参数通过<constructor-arg />元素提供,与实际使用构造函数完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同,尽管在此示例中。将以基本相同的方式使用实例(非静态)工厂方法(除了使用factory-bean属性而不是类属性),因此这里不再讨论细节。