前言
- hello,大家好,我是 Lorin,大家使用 Spring 框架 @Autowired 注解字段注入时是不是经常遇到这个问题,今天我们来看看为什么?
Field injection is not recommended
版本
- JDK 8
- spring-boot-starter-parent 2.5.4
回顾一下 Spring 注入的三种方式
字段注入
public class TestService {
@Autowired
private DependencyA dependencyA;
@Autowired
private DependencyB dependencyB;
}
方法注入
public class TestService {
private DependencyA dependencyA;
private DependencyB dependencyB;
@Autowired
public void setDependencyA(DependencyA dependencyA) {
this.dependencyA = dependencyA;
}
@Autowired
public void setDependencyB(DependencyB dependencyB) {
this.dependencyB = dependencyB;
}
}
构造器注入
public class TestService {
private final DependencyA dependencyA;
private final DependencyB dependencyB;
// @Autowired 可以省略
@Autowired
public TestService(DependencyA dependencyA, DependencyB dependencyB) {
this.dependencyA = dependencyA;
this.dependencyB = dependencyB;
}
}
为什么不推荐 @Autowired 字段注入
- 从上面我们可以看到字段注入是最简单且无其它额外代码的方式,但同时也存在以下问题:
组件与特定 IOC 框架强耦合(最主要原因)
- 组件与特定 IOC 框架强耦合,导致使用其它 IOC 容器框架时很难兼容。同时也导致单元测试必须使用特定框架实现组件管理,不容易进行单元测试。
无法实现像构造器注入不可变对象
使用字段注入的依赖对外部不可见
容易使对象违反单一职责原则
- 由于字段注入使用过于简单,容易使对象过分膨胀,违反单一职责原则。
@Autowired VS @Resource
- @Autowired 是Spring定义的,而 @Resource 是JSR-250定义。
- 依赖识别方式:@Autowired默认是byType,可以使用@Qualifier指定Name,@Resource默认使用ByName,如果找不到则使用ByType。
- 适用对象:@Autowired可以对构造器、方法、字段使用,@Resource只能对方法、字段使用。
@Resource 为什么没有不推荐
- 其实这是因为 @Autowired 是 Spring 提供的特定注解,和 Spring 框架绑定,而 @Resource 是JSR-250提供的,它是Java标准,作为 IOC 容器框架都是需要实现的,所以不存在上面的第一点问题。
一些最佳实践
- Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property a required dependency.
- 翻译: 我们可以使用混合使用构造器注入和方法注入,一个好的经验法则是对于强制依赖使用构造器注入,而非强制依赖使用方法注入。在方法注入上使用 @Required 将依赖标志为非必填。
public class TestService {
/**
* 必须依赖
*/
private final DependencyA dependencyA;
/**
* 非必须依赖
*/
private DependencyB dependencyB;
public TestService(DependencyA dependencyA) {
this.dependencyA = dependencyA;
}
@Autowired(required = false)
public void setDependencyB(DependencyB dependencyB) {
this.dependencyB = dependencyB;
}
使用构造器注入带来的循环依赖问题
- 如果我们使用构造器注入,那么可能会出现无法解析循环依赖的问题。这里提供两个解决方案:
使用方法注入(官网推荐)
- One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
使用 Lazy 注解(推荐但谨慎使用)
- 一些场景下会导致初次请求处理变慢。
@Service
public class DependencyA {
private final TestService testService;
public DependencyA(@Lazy TestService testService) {
this.testService = testService;
}
}
@Service
public class TestService {
private final DependencyA dependencyA;
public TestService(DependencyA dependencyA) {
this.dependencyA = dependencyA;
}
}