引言
我们平时做项目开发的时候,都比较喜欢使用@Autowired注解来注入依赖,但是大家有没有留意到一个告警:Field injection is not recommended
@Autowired注解注入弊端
该注解注入本质上是通过反射的方式注入,但是属性注入有以下弊端:
1.容易引发空指针异常
Java 在初始化一个类时,是按照静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。所以在执行这个类的构造方法时,user对象尚未被注入,它的值还是 null,这可能导致运行时的空指针异常。对于必须存在的依赖,建议采用@NonNull
结合@Autowired
,或者直接使用构造器注入,这样在初始化阶段就能发现问题,而不是在运行时。
@Autowired
private User user;
private String company;
public UserDaoImpl(){
this.company = user.getCompany();
}
编译过程不会报错,但是运行之后报NullPointerException
Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException
2.初始化顺序不确定性
Spring容器对Bean的初始化顺序默认是无序的,若多个Bean间存在循环依赖或者依赖特定初始化顺序的情况,仅依赖@Autowired
可能无法确保依赖项按照预期的顺序初始化。这时,明确的构造函数注入或使用@Order
、@DependsOn
等注解控制初始化顺序更为可靠。
3.可测试性降低
在单元测试中,我们通常希望模拟依赖对象以隔离待测试对象。如果过度依赖于@Autowired
,那么在编写测试用例时,可能会因为Spring容器的介入而使得单元测试变得复杂且低效。相比之下,通过显式定义构造器注入或setter注入,可以更方便地替换为Mock对象进行测试。
4.隐式耦合
@Autowired
注解使得类与它的依赖关系之间形成了隐式的强耦合。尽管依赖注入有助于降低模块间的耦合度,但过度依赖注解则可能导致类的设计更难以理解和修改。当查看一个类时,如果没有明确的构造函数或者setter方法显示其依赖项,开发者可能需要花费更多时间去寻找和理解这些依赖关系。
spring官方建议
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. 翻译过来就是: 强制依赖就用构造器方式注入;可选、可变的依赖就用setter注入.比如:
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
但是这种方法有个缺点,如果需要注入的类很多的时候,这个构造方法就会变得很大,可读性以及后期可维护性太差,容易造成遗漏。
如何解决这个问题呢?
可以使用@RequiredArgsConstructo注解解决,这种形式就是Spring推荐使用的构造器方式注入,此种方式是lombok包下的注解,如果使用此种方式,需要项目中引入lombok,例如:
@RequiredArgsConstructor
public class UserDaoImpl{
private final UserService userService;
}
在这里有个注意的地方:要加上final关键字,否则会注入不成功,运行时报空指针异常