标签:
本文将继续前文,描述Spring IoC中的依赖处理。
一般的企业应用也不会只有一个对象(或者是Spring Bean)。甚至最简单的应用都要有一些对象来协同工作来让终端用户看到一个完整的应用。下一部分将解释开发者从单独的定义Bean,到让这些对象在一个应用中协同工作。
依赖注入是一个让对象只通过构造参数,工厂方法的参数或者配置的属性来定义他们的依赖。这些依赖也是对象所需要协同工作的对象。容器在之后会在创建Bean的时候注入这些依赖。整个过程完全反转了Bean自己控制实例化或者,这个过程也称之为控制反转。
当使用了依赖注入的准则以后,在管理解耦对象之前的依赖上面,代码更加的简单。对象不再关注依赖,也不需要知道依赖类的位置。这样的话,你的类跟容易测试,尤其是你的依赖是接口或者抽象类的情况,开发者可以轻易在单元测试中mock对象。
依赖注入主要使用两种方式,一种是基于构造函数的注入,另一种的基于Setter方法的依赖注入。
基于构造函数的依赖注入
基于构造函数的依赖注入是由容器来调用构造函数,构造函数的参数代表这个Bean所依赖的对象。跟调用带参数的静态工厂方法基本一样。下面的例子展示了一个类通过构造函数来实现依赖注入的。需要注意的是,这个类没有任何特殊的地方,只是一个简单的不依赖于容器特殊接口,基类或者注解的普通类。
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的顺序就是这些参数实例化,装载的顺序。参考如下代码:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
没有不清晰的语义,假设Bar
和Baz
继承层次上不想管的话。那么下面的配置完全可以工作正常,开发者不需要去在<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>
当引用另一个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>
或者使用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
注解来显式声明构造函数的名称,比如如下代码:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter方式的依赖注入
基于Setter函数的依赖注入是容器在调用开发者定义的Bean的无参构造函数,或者无参数的工厂方法以后来调用Setter方法来实现的依赖注入。
下面的例子展示了使用Setter方法进行的依赖注入,下面的类对象只是简单的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方式在通过构造函数注入依赖之后再次注入依赖。开发者在BeanDefinition
中可以使用PropertyEditor
实例来自由选择注入的方式。然而,大多数的开发者并不直接使用这些类,而是跟喜欢XML形式的bean
定义,或者基于注解的组件(比如使用@Component
,@Controller
等)或者在配置了@Configuration
的类上面使用@Bean
的方法。
基于构造函数还是基于setter方法?
因为开发者可以混用两者,所以通常比较好的方式是通过构造函数注入必要的依赖通过setter方式来注入一些可选的依赖。其中,在Setter方法上面的@Required
注解可用来构造必要的依赖。
Spring队伍推荐基于构造函数的注入,因为这种方式会使促使开发者将组件开发成不可变对象而且确保了注入的依赖不为null
。而且,基于构造函数的注入的组件被客户端调用的时候也是完全构造好的。当然,从另一方面来说,过多的构造函数参数也是非常差的代码方式,这种方式说明类貌似有了太多的功能,最好重构将不同职能分离。
基于Setter的注入只是用于可选的依赖,但是也最好配置一些合理的默认值。否则,需要对代码的依赖进行非NULL的检查了。基于Setter方法的注入有一个便利之处在于这种方式的注入是可以进行重配置和重新注入的。
依赖注入的风格适合大多数的情况,但是有时,使用第三方的库的时候,开发者可能并没有源码,而第三方的代码也没有setter方法,那么就只能使用基于构造函数的依赖注入。
依赖解析过程
容器对Bean的解析如下:
ApplicationContext
。配置元数据可以通过XML, Java 代码,或者注解。int
,long
,String
,boolean
等。Spring容器会在容器创建的时候针对每一个Bean进行校验。然而,Bean的属性在Bean没有真正创建的时候是不会配置进去的。单例类型的Bean是容器穿件的时候配置成预实例转台的。Bean的Scope
在后续有介绍。其他的Bean都只有在请求的时候,才会创建。显然创建Bean兑现会有一个依赖的图。这个图表示Bean之间的依赖关系,容器根据此来决定创建和配置Bean。
循环依赖
如果开发者主要使用基于构造函数的依赖注入,那么很有可能出现一个循环依赖的场景。
比如说:类A在构造函数中依赖于类B的实例,而类B的构造函数依赖类A的实例。如果你这么配置类A和类B相互注入的话,Spring IoC容器会发现这个运行时的循环依赖,并且抛出BeanCurrentlyInCreationException
。
开发者可以通过使用Setter方法来配置依赖注入,这样可以解决这个问题。或者就不使用基于构造函数的依赖注入,仅仅使用基于Setter方法的依赖注入。换言之,尽管不推荐,但是开发者可以将循环依赖配置为基于Setter方法的依赖注入。
开发者可以相信Spring能正确处理Bean。Spring能够在加载的过程中发现配置的问题,比如引用到不存在的Bean或者是循环依赖。Spring会尽可能晚的在Bean创建的时候装载属性或者解析依赖。这也意味着Spring容器加载正确后会在Bean注入依赖出错的时候抛出异常比如,Bean抛出缺少属性或者属性不合法。这延迟的解析也是为什么ApplicationContext
的实现会令单例Bean处于预实例化状态。这样,通过ApplicationContext
的创建,可以在真正使用Bean之前消耗一些内存代价发现配置的问题。开发者也可以覆盖默认的行为让单例Bean延迟加载,而不是处于预实例化状态。
如果不存在循环依赖的话,Bean所引用的依赖会优先完全构造依赖的。举例来说,如果Bean A依赖于Bean B,那么Spring IoC容器会先配置Bean B,然后调用Bean A的Setter方法来构造Bean A。换言之,Bean先会实例化,然后配置依赖,然后相关的生命周期方法才会调用。
依赖注入的例子
下面的例子会使用基于XML配置的元数据,然后使用Setter方式进行依赖注入。代码如下:
<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文件中相一致,下面的例子是基于构造函数的依赖注入
<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
的依赖。
下面的例子,是通过静态的工厂方法来返回Bean实例的。
<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;
}
}
工厂方法的参数,也是通过 <constructor-arg/>
标签来指定的,和基于构造函数的依赖注入是一致的。之前有提到过,返回的类型不需要跟exampleBean
中的class
属性一致的,class
指定的是包含工厂方法的类。当然了,上面的例子是一致的。使用factory-bean
的实例工厂方法构造Bean的,这里就不多描述了。
标签:
原文地址:http://blog.csdn.net/ethanwhite/article/details/51372718