Field injection is not recommend
运行 IDE 的自动检查工具分析代码时, 如果用@Autowired 注解的话,会提示如下的警告:
![](https://img.haomeiwen.com/i13872265/3dd79770413464e1.png)
第一次看到这样的提示,是很困惑的,因为通常情况下直接使用@Autowired 不仅让代码更加简洁易读,写起来也十分的方便。
虽然最新(5.1.9)的Spring 文档依赖注入的章节里只介绍了两种依赖注入的方法,但实际上有三种依赖注入的方式:
- Constructor-based dependency injection(基于构造方法的依赖注入)
- Setter-based dependency injection(基于 setter 的依赖注入)
- Field-based dependency injection(基于 filed 注解的依赖注入)
第三种依赖注入的方式是代码分析工具不建议的,但也是使用最多、最常见的依赖注入方式。即使在 Spring 官方的一些手册里(比如Accessing data with MySQL),也可以看到使用 Field-based dependency injection。
下面分别具体介绍一下这三种依赖注入的方式。
三种依赖注入的方式
-
Constructor-based dependency injection
@Component public class ConstructorBasedInjection { private final InjectedBean injectedBean; @Autowired public ConstructorBasedInjection(InjectedBean injectedBean) { this.injectedBean = injectedBean; } }
基于构造方法的依赖注入的主要优点是,可以注入声明为 final 的字段。
基于构造方法的依赖注入在类实例化期间启动,对于必要的依赖项来说,使用构造方法注入会更合适。 -
Setter-based dependency injection
@Component public class ConstructorBasedInjection { private InjectedBean injectedBean; @Autowired public void setInjectedBean(InjectedBean injectedBean) { this.injectedBean = injectedBean; } }
如果使用无参数构造方法或无参数静态工厂方法实例化 Bean,Spring 容器将调用这些 setter 方法,注入 Bean 的依赖项。
-
Field-based dependency injection
@Component public class ConstructorBasedInjection { @Autowired private InjectedBean injectedBean; }
使用基于字段的依赖注入的话,只需在需要注入的字段加上@Autowired,Spring 容器就会在类初始化的时候设置这些字段。
可以看到,这种方法确实是最简洁的,不需要任何模版代码。但是为什么代码检查还是还是不建议这种方法呢?因为它确实存在着一些缺点。
Field-based dependency injection 的缺点
-
不允许 Immutable 字段的声明
基于字段的依赖注入不支持声明为 final 的字段,声明为 final 的字段必须在类初始化的时候初始化该字段。如果声明了 final 的字段并想注入该依赖,唯一的方式是使用基于构造方法的注入
-
可能会违反单一职责原则
在面向对象的设计原则中,我们经常会提到 SOLID,更好地遵循 SOLID 原则能让我们的代码更好理解、维护,拓展性更强。其中 S 指的是单一职责原则,也就是一个类应该只负责整个工程的单个功能部分。
如果使用基于字段依赖注入的话,即使这个类依赖了很多其它的类,也经常觉得没什么问题。而如果使用基于构造方法的依赖注入的话,会很容易发现构造方法传入了太多的参数(有的人觉得这是基于构造方法依赖注入方法的缺点,事实上出现这种情况时,是一个代码需要重构的提醒),这个时候我们可以审视我们的代码,是否需要将该类拆分重构。
使用基于字段的依赖注入虽然没有直接违背单一职责原则,但确实隐藏了发现违背单一职责原则的一些信号。
-
与依赖注入的结合过分紧密
使用基于字段的依赖注入的主要原因是能够减少代码,让代码简洁。但这同时也意味着设置这些字段的唯一方法是通过 Spring 容器实例化类并使用反射注入它们,否则字段将不会初始化,该类也无法使用。
如果要在 Spring 容器外部使用这些类,比如单元测试,则必须使用 Spring 容器来实例化类,没有其他方法(除了反射)来设置这些字段。
而如果使用基于构造方法或者基于 setter 依赖注入时,在单元测试时,我们可以 mock 依赖的对象,并将它们传到构造方法或 setter 方法中,会让单元测试简单很多。 -
隐藏了依赖关系
使用基于构造方法依赖注入,或者基于 setter 的依赖注入时,外部可以通过构造方法或者 setter 方法知道该类的依赖项。
而如果使用基于字段的依赖注入时,该类的所有依赖对于外部来说是不可知的。
结论
基于字段的依赖注入虽然用起来十分方便,代码也十分简洁,但确实存在着一些缺点,这也是为什么代码检查工具不推荐这种写法的原因。
通常情况下,如果有 final 字段,或者有必需的依赖项,建议使用基于构造方法的依赖注入。基于 setter 的依赖注入通常建议用来注入可选的依赖项。