Autowired放成员变量上还是构造函数上?

前言

如果你是通义千问的忠实粉丝,你会发现有时候他会建议你把成员变量的依赖注入修改为构造函数的依赖注入,就像这样:

1
2
3
4
5
6
7
8
@Service
public class SSHServiceImpl implements SSHService {
private final SSHProperties sshProperties;
@Autowired
public SSHServiceImpl(SSHProperties sshProperties) {
this.sshProperties = sshProperties;
}
}

有什么区别吗?

就结果而言,没太大差异

这两者其实在最终的依赖注入结果上没有很大的差异,总之就是注入了。

在过程上,稍微有点区别

但是呢,在过程上,稍微有这么几个小区别:

  • 直接注入成员变量不会执行构造函数,而直接为对象赋值;构造函数注入要求Bean实例化之前完成,即产生构造函数的执行过程;
  • 刚刚提到直接注入成员变量过程对对象的赋值过程,这也意味着注入允许对象的某个变量直接被替换,这是很操蛋的。而构造函数注入避免了这一点,默认值就是默认值,使得类与类之间变得内聚。
  • 构造函数注入可以解决循环依赖的问题,但是成员变量注入不能;

一些个人的理解

在看到本文之前,如果你已经查阅了较多的资料,有了自己的知识储备,你可能会有一些疑问,我也是。所以这里给出我个人的见解。

依赖可见性

有些人可能会说存在依赖可见性的差异。因为构造函数注入其实隐形的要求我们将注入的成员变量设置为private,以保证依赖的可见性受限。当然,目前为止规范就是用private注入,所以这一点差距不大。

错误检测

也有人会说存在错误检测的差异。因为当Bean匹配失败的时候,成员变量注入会尝试使用默认值,构造函数注入会直接报错,让问题出现地更早,方便定位,作用类似于Object.requireNonNull一样。但是呢,毕竟没谁有事没事就直接设置默认值。为了方便,大家都是直接将默认值放在application.yml中,而不会在注入的过程中再一次指定默认值。这只是一个比较单纯的习惯问题,反而避免了这个差异(欸嘿~⭐)。

为什么会让类之间内聚

为什么会提到内聚,实际上就是说明确类与类之间的分工。而如果一个类好好的,突然又在某个不知名的角落中被注入了奇奇怪怪的东西,虽然能提升程序员的不可替代性,但是让这个项目直接向着操蛋的方向一路飞奔。

循环依赖问题为什么能够被解决

循环依赖问题,这个是Spring的老生常谈了,具体看看Spring解决循环依赖的主要方式就行了,主要包括:

  • Bean工厂初始化
  • 提前暴露
  • 循环依赖检测
  • 延迟初始化
  • 初始化部分Bean
  • 延迟填充
  • 完善Bean

其中,初始化部分Bean和延迟填充有时候也被整合为懒加载,是解决循环依赖的关键。

如果熟悉这些的话,构造函数注入能够解决循环依赖问题,实际上也是得益于Spring本身的特点。