依赖注入(DI)是一个【过程】(目前可以理解为给成员变量赋值的过程),在此过程中,对象仅通过【构造函数参数】、【工厂方法参数】等来确定它们的依赖项。 然后容器在创建bean时注入这些依赖项。 从根本上说,这个过程与bean本身相反(因此得名“控制反转”)。
使用依赖注入的代码更清晰,并且在向对象提供依赖时【解耦更有效】。
DI主要有以下两种方式:
- Constructor-based依赖注入,基于构造器的依赖注入,本质上是使用构造器给成员变量赋值。
- Setter-based依赖注入,基于setter方法的依赖注入,本质上是使用set方法给成员变量赋值。
#1、基于构造函数的依赖注入
1、使用参数的顺序实现
如果beanDifination的构造函数参数中不存在【潜在的歧义】,那么在beanDifination中定义【构造函数参数的顺序】就是在实例化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>
2、构造函数参数类型匹配
当引用另一个bean时,类型是已知的,可以进行匹配(如上例所示)。 当使用简单类型时,例如<value>true</value>
, Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。 考虑以下官网提供的类:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final 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>
2、基于setter的注入
基于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】支持它管理的bean的【基于构造函数】和【基于setter】的依赖注入。 在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。也就意味着先通过有参构造构建对象,再通过setter方法进行特殊值的赋值。
下面的元数据配置示例为【基于setter】的DI方式:
<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"/>
3、基于构造函数还是基于setter的依赖注入?
由于您可以混合使用基于构造函数和基于setter的DI,一般情况下,我们对于【强制性依赖项】使用构造函数,对于【可选依赖项】使用setter方法注入,这是一个很好的经验法则。 注意,在setter方法上使用【@Required】注解可以使属性成为必需依赖项。
Spring团队通常提倡构造函数注入,因为它允许你将应用程序组件实现为不可变的对象,并确保所需的依赖项不是”空“的。 而且,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。
Setter注入主要应该只用于可选依赖项,这些依赖项可以在类中分配合理的默认值。 setter注入的一个好处是,setter方法使该类的对象能够在稍后进行重新配置或重新注入。
有时,在处理您没有源代码的第三方类时,您可以自行选择。 例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式
(7)带有【p命名空间】的XML配置方式
第一步:先在头部文件导入依赖:
xmlns:p="http://www.springframework.org/schema/p"
使用: P:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
8)带有c命名空间的XML快捷方式
与带有p-名称空间的XML配置方式类似,在Spring 3.1中引入的【c-名称空间】允许内联属性来配置构造函数参数,而不是嵌套的【constructor-arg】元素。
下面的例子使用了【c: 命名空间】来完成与【基于构造器的依赖注入】:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
【c: 命名空间】通过名称设置构造函数参数。 类似地,它需要在XML文件中声明对应的命名空间。
对于【构造函数参数名不可用的罕见情况】(通常是在没有调试信息的情况下编译字节码),可以使用回退参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
(10)延迟初始化的 Bean
默认情况下,【ApplicationContext】实现会作为初始化过程的一部分,会在容器初始化的时候急切地创建和配置所有【singleton bean】。 通常,这种预实例化是可取的,因为配置或周围环境中的错误可以被立马发现,而不是几个小时甚至几天之后(调用一个方法,创建一个实例的时候等)。 当这种行为不可取时,您可以通过将beanDifination标记为【惰性初始化】来防止【单例bean的预实例化】。 延迟初始化的bean告诉IoC容器在【第一次请求】时创建bean实例,而不是在启动时。
在XML中,这种行为是由
<bean/>
元素上的【lazy-init】属性控制的,如下面的示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
然而,当一个【延迟初始化的bean】是一个没有延迟初始化的单例bean的依赖时,ApplicationContext会在启动时创建这个延迟初始化的bean,因为它必须满足单例bean的依赖, 延迟初始化的bean会被注入到没有延迟初始化的其他单例bean中。
你也可以在容器级通过在
<beans/>
元素上使用“default-lazy-init”属性来控制延迟初始化,如下面的例子所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>