问题
在使用 IDEA 开发 SpringBoot 项目时,在Controller类中使用注解 @Autowired 注入一个依赖出现了警告提示。这是怎么回事?
当我们鼠标停留在警告线处会出现提示:Field injection is not recommended(不建议Field注入)
Spring Bean 的注入方式
1.变量(Field)注
@RestController
@RequestMapping(value = "/test")
public class ParkMapController{
@Autowired
private ParkMapService parkMapService;
}
优点:
代码少,简洁明了
新增依赖十分方便,不需要修改原有代码
注入简单,只需要使用 @Autowired 注解或者 @Resource 注解
缺点:
容易出现空指针异常,Field 注入允许构建对象实例的时候依赖的示例对象为空,这就导致了空指针异常无法尽早的暴露出来,因为你不调用将一直无法发现NullPointException的存在
对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。对单元测试不友好,如果使用 Field 注入,那么进行单元测试就需要初始化整个Spring 环境,将所有 Bean 实例化
使用field注入会出现循环依赖的隐患
容易破坏单一职责原则
2.构造器注入
@RestController
@RequestMapping(value = "/valet/parking/")
public class CommonController
{
private final CommonService commonService;
@Autowired
public CommonController(CommonService commonService)
{
= commonService;
}
}
优点:
保证依赖不可变(final关键字)。
保证依赖不为空,强依赖处理,在编译阶段就能暴露出问题(省去了我们对其检查)。
保证返回客户端(调用)的代码的时候是完全初始化的状态,方便单元测试。
避免了循环依赖。
提升了代码的可复用性。
可以明确成员变量的注入顺序。
缺点:当注入参数较多时,代码臃肿,不够友好。
注入
@RestController
@RequestMapping(value = "/valet/parking/task/")
public class ParkTaskController
{
private ParkTaskService parkTaskService;
@Autowired
public void setParkTaskService(ParkTaskService parkTaskService) {
= parkTaskService;
}
}
优点:
- 相比构造器注入,set注入类似于选择性注入。
- 允许在类构造完成后重新注入。
缺点:暂无
构造函数注入 vs Setter注入
构造函数注入:
构造函数注入对于强制依赖项是有好处的。对象正常运行所需的依赖项通过在构造函数中提供这些参数,可以确保在构造对象时就可以使用它。构造函数中分配的字段也可以是final,允许对象是完全不可变的,或者至少保护其必需的字段。
使用构造函数提供依赖关系的一个后果是,以这种方式构造的两个对象之间不再可能存在循环依赖关系(与setter注入不同)。这实际上是一件好事,而不是一种限制,因为应该避免循环依赖,这通常是糟糕设计的标志。这样就可以避免这种做法。
另一个优点是,如果使用spring 4.3+,可以完全将类与DI框架解耦。原因是Spring现在支持一个构造函数场景的隐式构造函数注入。这意味着不再需要在类中使用DI注解。当然,也可以通过在spring配置中为给定的类显式配置DI来实现相同的目的,这只是使整个过程变得更加简单。
官方建议从开始,Spring文档鼓励使用构造函数注入也没有否认setter注入的好处:
Setters:
使用setter注入依赖项。类应该能够在不提供它们的情况下工作。在对象实例化之后,可以随时更改依赖项。根据具体情况,这可能不是一个优势。有时,拥有一个不可变的对象是可取的。有时在运行时更改对象的协作者是很好的——例如JMX托管的mbean。
官方建议从开始。文档鼓励使用setter而不是构造函数:
结论
应尽量避免Field注入。推荐使用构造函数或方法来注入依赖项。两者各有利弊,其用法取决于具体情况。但是,由于这些方法可以混合使用,所以这不是非必须选择一种,可以将setter和构造函数注入合并到一个类中。构造函数更适合于强制依赖项和以不变性为目标的情况。对于可选的依赖项,setter更好。