Spring 官方为什么建议构造器注入?
前言
本章的内容主要是想探讨我们在进行 Spring 开发过程当中,关于依赖注入的几个知识点。感兴趣的读者可以先看下以下问题:
@Autowired
,@Resource
,@Inject
三个注解的区别当你在使用 @Autowired
时,是否有出现过Field injection is not recommended
的警告?你知道这是为什么吗?Spring 依赖注入有哪几种方式?官方是怎么建议使用的呢?
@Autowired,@Resource,@Inject 三个注解的区别
@Autowired
, @Resource
, @Inject
三个注解进行依赖注入。下面来介绍一下这三个注解有什么区别。@Autowired
@Autowired
为Spring 框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired
。public interface Svc {
void sayHello();
}
@Service
public class SvcA implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service A");
}
}
@Service
public class SvcB implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service B");
}
}
@Service
public class SvcC implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service C");
}
}
@SpringBootTest
public class SimpleTest {
@Autowired
// @Qualifier("svcA")
Svc svc;
@Test
void rc() {
Assertions.assertNotNull(svc);
svc.sayHello();
}
}
type
在上下文中查找匹配的bean,查找type为Svc的beanname
进行匹配@Qualifier
注解,则按照@Qualifier
指定的name
进行匹配,查找name为svcA的bean@Autowired(required=false)
,如果设置required
为false
(默认为true
),则注入失败时不会抛出异常)@Inject
和@Autowired
是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor
来处理的。@Inject
是 JSR-330 定义的规范,如果使用这种方式,切换到Guice
也是可以的。Guice 是 google 开源的轻量级 DI 框架
@Inject
是 Java EE 包里的,在 SE 环境需要单独引入。另一个区别在于@Autowired
可以设置required=false
而@Inject
并没有这个属性。@Resource
@Resource
是 JSR-250 定义的注解。Spring 在 CommonAnnotationBeanPostProcessor
实现了对JSR-250
的注解的处理,其中就包括@Resource
。@Resource
有两个重要的属性:name
和type
,而Spring 将@Resource
注解的name
属性解析为bean的名字,而type
属性则解析为bean的类型。name
和type
,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。name
,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。type
,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常。name
,又没有指定type
,则默认按照byName
方式进行装配;如果没有匹配,按照byType
进行装配。@Autowired
注解的时候,你会发现 IDEA 会有警告提示:Field injection is not recommended Inspection info: Spring Team Recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies
不建议使用基于 field 的注入方式。Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。
@Service
public class HelpService {
@Autowired
@Qualifier("svcB")
private Svc svc;
public void sayHello() {
svc.sayHello();
}
}
public interface Svc {
void sayHello();
}
@Service
public class SvcB implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service B");
}
}
@Autowired
处,使用Alt + Enter
快捷进行修改之后,代码就会变成基于 Constructor 的注入方式,修改之后:@Service
public class HelpService {
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
// Assert.notNull(svc, "svc must not be null");
this.svc = svc;
}
public void sayHello() {
svc.sayHello();
}
}
svc
是必须的依赖,应该使用Assert.notNull(svc, "svc must not be null")
来确认。基于 field 注入(属性注入) 基于 setter 注入 基于 constructor 注入(构造器注入)
1. 基于 field 注入
@Autowired
private Svc svc;
2. 基于 setter 方法注入
通过对应变量的setXXX()
方法以及在方法上面使用注解,来完成依赖注入。比如:
private Helper helper;
@Autowired
public void setHelper(Helper helper) {
this.helper = helper;
}
注:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不写的。
3. 基于 constructor 注入
将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如:
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
this.svc = svc;
}
在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。
基于 field 注入的好处
@Autowired
扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。基于 field 注入的坏处
成也萧何败也萧何
容易违背了单一职责原则 使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。但是当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现 something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)。 依赖注入与容器本身耦合 依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的 POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。 这个问题具体可以表现在: 你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试 你的类和依赖容器强耦合,不能在容器外使用 不能使用属性注入的方式构建不可变对象( final
修饰的变量)
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 注入 当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter 注入更适合可变性的注入。
让我们看看 Spring 这样推荐的理由,首先是基于构造方法注入,
final
修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service
),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。而对于基于 setter 的注入,他们是这么说的:
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。
小结
参考
转自:Richard_Yi 来源:https://juejin.cn/post/6844904056230690824
PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。
(完) 加我"微信" 获取一份 最新Java面试题资料 请备注:666,不然不通过~
最近好文
1、Spring Boot 实现扫码登录,这种方式太香了!!
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。 获取方式:关注公众号并回复 java 领取,更多内容陆续奉上。 明天见(。・ω・。)ノ♡
评论