1、Spring实现依赖注入的方式有哪些?
这个问题似乎很简单,网上随便找一找,关于这个问题的资料便有一大堆。其中,被广泛使用的答案是:存在三种方式,有构造器注入、set方法注入、属性注入。
笔者之前也是这么认为的,但是不知道大家在使用idea开发的时候,是否有见过如下提示呢?
这个提示的意思是:不推荐使用属性注入
。这是为什么呢?
2、什么是依赖注入
我们先来回顾下,什么是依赖注入。要提这个,就不得不也提一下控制反转。这两个是spring中最核心的功能。
控制反转是指将对象或者程序的部分控制权转移到容器或者框架中,即将对象的生命周期与对象间依赖关系交由spring容器控制。这个依赖关系主要包含其他bean、组件、配置等。
依赖注入则是指在运行时动态的将依赖关系注入到对象中。属于控制反转的一部分,由框架负责处理对象的依赖关系。(被反转的是:设置对象的依赖)
举一个简单的例子解释一下:
这是一段删除用户的Controller层代码,我把它改造了一下:
public class UserMain {
public static void main(String[] args) {
UserController userController = new UserController();
userController.setUserApp(new UserApp());
userController.deleteUser(1L);
}
}
在main函数的内部,仅有三行代码:
第一行是为了实例化对象;
第二行则是设置该对象所需要的UserApp对象;
第三行则是执行具体的方法了。
在没有Ioc与DI之前,我们想要执行一个功能的话,需要经过如上的三步。但是有了Ioc和DI之后呢?
public class UserMain {
public static void main(String[] args) {
Context.get(UserController.class).deleteUser(1L);
}
}
Spring通过Ioc控制了对象生命周期中的实例化阶段,取代了第一行代码;通过DI来注入对象的依赖关系,设置该对象所依赖的其他的对象,取代了第二行代码。所以,最终,我们只需要从Spring容器中获取到最终被装配完善的Bean,就可以直接使用了。
3、Spring推荐使用的是什么方式呢?
继续回到我们之前话题,为什么Spring官方不推荐使用属性注入呢?
相关的内容大家可以直接看下官方的文档:https://docs.spring.io/spring-framework/docs/5.3.39/reference/html/core.html#beans-factory-autowire
简单点讲属性注入有以下几点劣势:
1.优先级问题,会被来自构造函数注入,或者显式的手工注入覆盖;
2.无法处理基本类型
3.依赖关系不明显,尤其是对一些文档工具
4.如果存在了多个相同类型的bean,可能会注入失败(Autowired注解支持byName、byType,但是如果二者都没有找到候选bean的话,会导致最终DI失败,抛出异常)
4、为什么推荐使用构造器注入与set方法注入呢?
4.1、构造器注入的优缺点
构造器注入更加显式的指出了bean之间的依赖关系,而且强制约束了依赖的bean不能为空,支持将依赖的bean属性设置为final(这是最核心的有点:不可变性与线程安全性)。而且构造方法注入暗指了二者之间存在了强依赖关系,使用了构造器注入方式的bean,无法在依赖bean没有实例化之前就创建自身。
但是这种强依赖关系,又导致了构造器注入遇到解决循环依赖问题时,没有任何办法可以解决。
Spring解决循环依赖的方式,是将bean的暴露时间提前。将仅完成了实例化的对象就暴露出来,而不是要等一个bean完全装配完成,经过了完整的实例化、DI、初始化阶段,才暴露出来。此时的bean,内部的属性、内部依赖的组件等,可能都还没有值。但是不影响处理bean之间的依赖关系。
而这也直接导致了构造器注入会受到循环依赖的影响,因为构造器就是用来实例化bean的,所以构造器注入,相较于别的注入方式,会在实例化阶段,就更早的检验bean之间的依赖关系。因此,spring现在的处理循环依赖的措施,对构造器注入就不起效果了。
此时就需要使用set方法注入了。set方法注入生效于DI阶段,此时bean已经实例化完毕,并不会受到循环依赖的影响。
4.2、set方法注入优缺点
灵活性,允许在对象创建之后,也可以修改对象间的依赖,但是这也可能导致整个程序行为的不可控,因为无法约束实际注入的组件到底是哪个,存在被修改的可能性;
可选依赖,相较于构造函数注入,依赖项不能为空的强制约束,set方法注入允许注入一个空对象,但这同时也意味着,在使用内部的某个组件时,需要处理该组件可能为空的情况;
4.3、autowired注解的优缺点
Autowired注解的使用,过于自由、约束较少,这带来了高度的灵活性。但同样也是因为这个设计,导致了Autowired注解很容易被滥用,更容易导致bean的职责不再单一、bean之间的依赖关系不够清晰。
这其实也是我们在编码时常常需要考虑的点:灵活性是否一定越高越好呢?
5、题外话
DI阶段是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean实现的 Autowired
注解与set方法注入都是在该方法内部被处理的,分别对应了:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
,
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues
。
有兴趣的可以debug看下实际的调用流程。