Spring依赖注入三种方式的思考和理解
前言
Spring框架的依赖注入特性非常灵活好用,并且提供了三种注入方式—Field注入、Constructor注入、Setter方法注入,但是目前各论坛有大量关于注入方式优缺点的讨论,这里只是记录我个人对于注入方式的思考及理解。
先放出个人对注入方式的思考和理解
- 尽量避免Field注入;
- 必要且不应被修改的依赖使用Constructor注入。配合Spring4.3及Lombok插件,代码会变得非常简洁,推荐大量使用;
- Setter注入仅用于静态变量的注入、解决依赖循环、需要灵活调整的依赖(非常少见)。同时需要注意安全性,应在Setter方法中验证调用栈的合法性;
三种注入方式对比
-
Field注入
@Autowired private SysUserService userService;
-
优点
- 增加修改都比较灵活;
- 注入简单,只需在字段上加@Autowired或@Resource;
- 美观,无需冗余代码
-
缺点
-
容易出现空指针异常,Field 注入允许构建对象实例的时候依赖的示例对象为空,这就导致了空指针异常无法尽早的暴露出来;
-
依赖不能被final修饰,可能在调用过程中被修改;
-
与DI高度耦合,脱离Spring容器后无法使用,不符合JavaBean的规范;
-
无法利用到Spring启动时的三级缓存,容易产生依赖循环;
-
对单元测试不友好,类必须通过反射实例化,单元测试时需要等待Spring实例化所有被管理的Bean;
-
-
-
Constructor注入
@Component // 任何高级@Component注解均可,比如@Service、@Controller、@Configuration public class SysUserController { private SysUserService userService; @Autowired // Spring4.3及更新的版本中,如果我们的类中只有单个构造函数,那么Spring就会实现一个隐式的自动注入,可以不写此注解。 public HelloBean(SysUserService userService) { this.userService = userService; } }
-
优点
- 可以利用到Spring的三级缓存,可以解决部分依赖循环;
- 将空指针等问题的暴露提前至编译阶段,更早暴露问题。
- 构造器生成的是初始化完全的对象;
- 依赖可以被final修饰;
- 可以明确指出依赖关系;
- 与DI解耦;
- 单元测试友好,只需创建相关依赖即可;
-
缺点
-
代码冗余,维护繁琐;
-
大部分依赖不会同时用到,相比Setter注入方式不够灵活;
-
CGLIB不能创建代理,迫使你使用基于接口的代理或虚拟的无参数构造函数;
-
-
-
Setter注入
private SysUserService userService; @Autowired public void setDependencyB(SysUserService userService) { this.userService = userService; }
- 优点
- 完全免疫依赖循环;
- 灵活,可以随时改变依赖;
- 只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;
- 将空指针等问题的暴露提前至编译阶段,更早暴露问题。
- 与DI解耦;
- 缺点
- 依赖不能被final修饰,可能在调用过程中被修改;
- 代码冗余,维护繁琐;
- 优点
Lombok + Constructor注入(Spring4.3及更新版本)
在Spring 4.3 以后,如果我们的类中只有单个构造函数,那么Spring就会实现一个隐式的自动注入,此时加上Lombok插件的@RequiredArgsConstructor注解1,代码会变得非常简洁
@Component // 任何高级@Component注解均可,比如@Service、@Controller、@Configuration
@RequiredArgsConstructor
public class SysUserController {
private final SysUserService userService;
}
Spring官方文档对于注入方式的推荐
在Spring 3.x 中,Spring团队建议使用setter来注入:
有大量参数时,Constructor会很笨拙,特别当属性不必须时。而使用Setter方法可以灵活的调用以改变依赖。
一些纯粹主义者(???)更喜欢Constructor注入,Constructor提供了所有的依赖,这意味着构造器生成的是初始化完全的对象,缺点是对象变得难以重新配置和改变依赖。
而在Spring4.x中,Spring团队建议使用Constructor注入:
Constructor注入允许成员变量被final修饰,并确保所依赖项不为空。此外,构造器生成的是初始化完全的对象。并且在Constructor中有大量参数是很糟糕的代码习惯,会体现出这个类有太多职责,不符合Java单一职责原则,应该被重构。
Setter注入应且只应用于可设置合理默认值且不必须的依赖(浓浓的翻译腔),另外,非空检查必须作用在所有依赖上。Setter注入的一个好处是可以灵活的调用以改变依赖。
结语
码字不易,如有其他看法,欢迎留言或私信讨论。如果解决了你的一些困惑或对你有所帮助,点个赞再走吧。
1:@RequiredArgsConstructor注解修饰的类在编译时会生成一个带有必要参数的构造器,必要参数为带有@NotNull注解的字段、被final修饰的字段 ↩︎