Spring官方为什么建议构造器注入?
Java后端技术
共 8797字,需浏览 18分钟
·
2021-08-12 11:51
往期热门文章: 2、14 个经典的 Linux 终端命令行,这些工具堪称神器!
3、Java 8 失宠!开发人员向 Java 11 转移...
转自:Richard_Yi
来源:https://juejin.cn/post/6844904056230690824
前言
@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的bean
如果有多个bean,则按照
name
进行匹配如果有
@Qualifier
注解,则按照@Qualifier
指定的name
进行匹配查找name为svcA的bean
如果没有,则按照变量名进行匹配
查找name为svc的bean
匹配不到,则报错。(
@Autowired(required=false)
,如果设置required
为false
(默认为true
),则注入失败时不会抛出异常)
@Inject
@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 注解。
@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 注入更适合可变性的注入。
final
修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service
),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。小结
参考
Setter-based dependency injection Field Dependency Injection Considered Harmful IDEA 警告 Field injection is not recommended
往期热门文章:
1、IntelliJ idea 高效使用教程,一劳永逸!
2、微软再出手,这次要干翻 IDEA 了。。 3、用好 Java 中的枚举,让你的工作效率飞起来! 4、还在用分页?太Low !试试 MyBatis 流式查询,真心强大! 5、告别 swagger-ui ,我选择了这款神器! 6、JDK/Dubbo/Spring 三种 SPI 机制,谁更好? 7、小团队真的适合引入Spring Cloud微服务吗? 8、人脸识别的时候,一定要穿上衣服啊!否则。。。 9、知乎高赞:PDD和国家电网,选哪个? 10、IDEA 中的热部署神器!
评论