实际的系统几乎不可能仅有单一的bean,都是很多个bean协作提供服务。本文目标也就是讨论如何冲破单一 bean 定义而让多 bean 协作实现系统。
1 什么是依赖注入(Dependency Injection)?
DI是一个过程。该过程中,bean可通过如下方式定义它们之间的依赖关系:
构造器参数
工厂方法参数
从工厂方法构造或返回的对象实例上设置的属性
接着,容器在创建bean时就会注入这些依赖关系。
该过程实质上就是 bean 本身操作的反转,因此得名 Inversion of Control(IoC,控制反转),而非对象自己直接通过使用其构造器或通过服务定位设计模式来控制其依赖项的实例化或位置。
使用 DI 代码会更整洁,当bean维护其依赖项时,也更解耦。bean不需要查找其依赖项,也无需知晓其依赖项的位置或具体类。如此一来,类也更便于测试,尤其是当依赖项为接口或抽象类时,可方便在UT中使用mock。
知晓了其原理了,那么在开发中又是如何实践的呢?
2 DI 的实现形式有哪些?
2.1 构造器注入
通过Spring容器调用具有多参数的构造器而完成,每个参数代表一个依赖项。调用具有特定参数的静态工厂方法来构造 bean 基本等效。
如下示例中的类仅可使用构造器注入的 DI:
2.1.1 构造器参数解析
构造器参数解析匹配通过 参数的类型 触发。若在 bean 定义的构造器参数中不存在歧义,则在 bean 定义中定义构造器参数的顺序是当 bean 实例化时这些参数提供给相应的构造器的顺序。说半天估计你也晕了,看如下案例:
假设 ThingSencond 和 ThingThird 类无继承关系,那么就没有歧义。因此,下面的配置也能工作良好,而无需在 <constructor-arg/> 标签中显式指定构造器参数的顺序或类型。
就像刚才的案例,当引用另一个bean时,类型已知,所以可以触发匹配。然而,当使用简单类型时,例如<value>true</value>, Spring无法确定值的类型,因此在没有帮助的情况下也就无法通过类型进行匹配。看如下案例:
2.1.2 构造器参数类型匹配
在前面的案例中,若使用 type 属性显式指定构造器参数的类型,则容器可以使用与简单类型相匹配的类型。如下所示:
2.1.3 构造器参数顺序
可使用 index 属性显式指定构造器参数的顺序,如下所示(注意从零开始计数)
除了解决多个简单值的不确定性,还解决了构造器具有相同类型的两个参数时的不确定性。
2.1.4 构造器参数名称
也可以使用构造器参数名称消除歧义,如下案例:
请记住,要使这一操作开箱即用,我们的代码必须在启用调试标识的情况下进行编译,以便Spring可以从构造器中查找参数名。
如果不能或不希望使用debug标识编译代码,可使用JDK的@ConstructorProperties 注解显式设置该构造函数的参数如何与构造对象的getter方法相对应。
看如下案例: