依赖
依赖注入
依赖注入是一个对象定义依赖的过程。即在对象构造或通过工厂方法返回之后,将对象运作时需要用到的其他对象,通过构造器参数、工厂方法参数、对象中的属性设置进对象实例之中。
容器在创建一个bean之间,将依赖注入其中。
依赖注入是一个基础性的反转,是将bean对自身的实例化、bean对自身上的依赖通过使用类构造或服务定位模式进行的定位过程进行了反转。
使用DI会使代码更加干净,并且当使用依赖所提供的对象,解耦和也更有效。
对象不需要查阅依赖,也不需要知道依赖的类或定位。
这样代码更容易测试,特别是当依赖是允许用于单元测试的stub与mock实现的抽象类或接口时。
基于构造器的依赖注入
基于构造器的依赖注入通过容器传入代表依赖的参数调用构造器的方式完成。
这种方法近似于通过特定的参数调用静态工厂方法。
public class ExampleA {
private ExampleB exampleB;//依赖
public ExampleA(ExampleB exampleB) {
this.exampleB = exampleB;
}
}
note:使用ExampleA的构造函数参数exampleB向ExampleB的依赖exampleB注入对象引用。
构造器参数的映射决定通过参数类型完成。
如果没有潜在的二义性存在于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>
ps:当构造器的多个参数的类型相同时,就会面临潜在的二义性问题。
ps:在构造器的多个参数的类型不同时,bean的子元素<constructor-arg>可以省略。
当另一个bean被引用时,只要这个bean的类型已知,映射便可以建立。
但当参数的类型是简单类型,传入值便需要手动指定传入值的类型。
package com.hk;
public class Example{
private int i;
private String s;
public Example(int i, String s) {
this.i = i;
this.s = s;
}
}
<bean id="example" class="com.hk.example">
<constructor-arg type="int" value="123" />
<constructor-arg type="java.lang.String" value="ccc" />
</bean>
note:通过com.hk.Example的构造器参数类型,匹配传入值。
<bean id="example" class="com.hk.example">
<constructor-arg index="0" value="123" />
<constructor-arg index="1" value="ccc" />
</bean>
note:通过com.hk.Example的构造器参数的位置,匹配传入值。
<bean id="example" class="com.hk.example">
<constructor-arg name="i" value="123" />
<constructor-arg name="s" value="ccc" />
</bean>
note:通过com.hk.Example的构造器参数的名称,匹配传入值。
ps:在使用构造器名称匹配传入值时,为了使这个工作更有意义,使代码必须在调试标志下可编译,此标志使得Spring可以从构造器中找到名称,即使用JDK注解@ConstructorProperties显示指定构造器参数名称。使用如下:
package com.hk;
public class Example{
private int i;
private String s;
@ConstructorProperties({"i", "s"})
public Example(int i, String s) {
this.i = i;
this.s = s;
}
}
基于setter的依赖注入
基于setter的依赖注入,通过容器在使用无参构造器或无参静态工厂方法实例化bean之后调用bean中的setter方法实现。
public class Example {
private Depen depen;//依赖
public void setDepen(Depen depen) {
this.depen = depen;
}
}
note:使用Example的setDepen方法注入depen依赖。
ApplicationContext支持基于构造器的依赖注入和基于setter的依赖注入。并且支持在构造器进行部分依赖的注入之后,再进行setter依赖注入。
使用BeanDefinition的形式配置依赖,即结合使用PropertyEditor实例将属性从一种形式转变为另一种形式。
Spring用户通常并不通过编程方法直接使用这些类,而是通过xml bean定义、组件注解与基于java的@Configuration类中的@Bean方法使用这些类。
这些源会在内部转变为BeanDefinition实例,并且被用于加载完整的Spring IoC容器实例。
当混合使用基于Setter的依赖注入与基于构造器的依赖注入时,最好用构造器注入必需的依赖,用setter方法或配置方法注入可选的依赖。
通过在setter方法上使用@Required注解,可以将setter方法注入的依赖指定为必需的依赖。
推荐使用构造器注入作为唯一的注入方式用于确保注入的依赖不为null,并且构造器注入会返回一个完整初始化状态的组件。当构造器中的参数过多,也意味着类承担了过多的指责并且需要重构。
setter注入应该只被用于可选依赖,即在类中会被赋予合理的默认值的依赖。并且,非空检测必须运行在每一处使用这些依赖的代码。setter注入的好处在于使对象可以重新配置或重新注入。因此,将setter注入用于设置JMX MBeans是一种不错的用法。
对于特定的类使用有意义的DI风格。例如,当第三方类没有暴露任何setter方法,那么构造器注入便是DI的唯一可用选择。
依赖解析过程
容器运行依赖解析的过程如下:
ApplicationContext被创建,并且使用描述了bean的配置元数据将其初始化。
对于每一个bean,它们的依赖被使用构造器参数、属性、静态工厂方法参数的形式表示。这些依赖将会在它们被确实创建之后被提供。
每一个属性或构造器参数是一个真是类型的值,或者是一个容器中其他bean的引用。
每一个属性或构造器参数的值可以从一个给定的指定类型转变为真实类型。Spring默认将string提供的值转变为内置类型。
Spring容器会在容器被创建的时候,验证每一个bean的配置。但是,直到这些bean被创建之后,这些bean的属性才会被设置。
单例作用域并且被设置为默认的预初始化的bean会在容器被创建时创建。除此之外,bean只会在被使用时被创建。
当一个bean被创建时,它的依赖,它的依赖的依赖等等都会被创建并赋值,此时一个beans的图将会被潜在地创建。
循环依赖
当主要使用构造器依赖时,可能会造成一个环形不可解依赖。eg:类A通过构造器注入类B,类B通过构造器注入类A。
Spring IoC容器会在运行时检测出这种状况,然后抛出一个BeanCurrentlyInCreationException。
一个解决方法是将一些类的源代码编辑成使用setter方法配置。或者是避免使用构造器注入,只使用setter注入。换句话说,即使这种注入方式不推荐,也可用用于配置环状依赖。
环形依赖强制要求一个bean在被注入另一个bean之前已经被完整初始化。
Spring可以在一个容器加载时,检测出配置问题。
Spring在创建bean之后,会尽可能晚地设置属性与依赖。所以,Spring会较晚地抛出一些异常,例如,如果属性为空的异常。
可以通过重写默认行为,令单例beans 懒惰实例化,而不是预实例化。
当一个协助bean注入另一个bean之前,这些协助bean的协助bean会先完全注入这些协助bean中。
package examples;
public class Example{
private Foo foo;
private String s;
private int i;
public void setI(int i) {
this.i = i;
}
public void setS(String s) {
this.s = s;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
}
<bean id="example" class="examples.Example">
<property name="foo">
<ref bean="foo"/>
</property>
<property name="s" ref="sss"/>
<property name="i" value="123"/>
</bean>
<bean id="foo" class="examples.Foo"/>
note:使用setter方法注入Example类的依赖。
package examples;
public class Example{
private Foo foo;
private String s;
private int i;
public static Example getInstance(Foo foo, String s, int i) {
this.foo = foo;
this.s = s;
this.i = i;
}
}
<bean id="example" class="examples.Example" factory-method="getInstance" >
<constructor-arg name="foo">
<ref bean="foo"/>
</constructor-arg>
<constructor-arg name="s" ref="sss"/>
<constructor-arg name="i" value="123"/>
</bean>
<bean id="foo" class="examples.Foo"/>
note:在静态工厂方法中注入依赖。
ps:这种创建bean的方式与实例方法创建bean的方法的区别在于,实例方法使用factory-bean属性,而不是class属性。