本章的内容主要是想探讨咱们在进行Spring 开发过程中,关于依赖注入的几个知识点。感兴趣的读者能够先看下如下问题:html
@Autowired
, @Resource
, @Inject
三个注解的区别@Autowired
时,是否有出现过Field injection is not recommended
的警告?你知道这是为何吗?若是你对上述问题都了解,那我我的以为你的开发经验应该是不错的👍。java
下面咱们就依次对上述问题进行解答,而且总结知识点。git
@Autowired
, @Resource
, @Inject
三个注解的区别Spring 支持使用@Autowired
, @Resource
, @Inject
三个注解进行依赖注入。下面来介绍一下这三个注解有什么区别。github
@Autowired
为Spring 框架提供的注解,须要导入包org.springframework.beans.factory.annotation.Autowired
。spring
这里先给出一个示例代码,方便讲解说明:app
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();
}
}
复制代码
装配顺序:ide
按照type
在上下文中查找匹配的bean单元测试
查找type为Svc的bean
复制代码
若是有多个bean,则按照name
进行匹配测试
若是有@Qualifier
注解,则按照@Qualifier
指定的name
进行匹配
查找name为svcA的bean
复制代码
若是没有,则按照变量名进行匹配
查找name为svc的bean
复制代码
匹配不到,则报错。(@Autowired(required=false)
,若是设置required
为false
(默认为true
),则注入失败时不会抛出异常)
在Spring 的环境下,@Inject
和@Autowired
是相同的,由于它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor
来处理的。
@Inject
是 JSR-330 定义的规范,若是使用这种方式,切换到Guice
也是能够的。
Guice 是 google 开源的轻量级 DI 框架
若是硬要说两个的区别,首先@Inject
是Java EE包里的,在SE环境须要单独引入。另外一个区别在于@Autowired
能够设置required=false
而@Inject
并无这个属性。
@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
进行装配。Field injection is not recommended
在使用IDEA 进行Spring 开发的时候,当你在字段上面使用@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();
}
}
复制代码
若是按照Spring 团队的建议,若是svc
是必须的依赖,应该使用Assert.notNull(svc, "svc must not be null")
来确认。
修正这个警告提示当然简单,可是我以为更重要是去理解为何Spring 团队会提出这样的建议?直接使用这种基于 field 的注入方式有什么问题?
首先咱们须要知道,Spring 中有这么3种依赖注入的方式:
所谓基于 field 注入,就是在bean的变量上使用注解进行依赖注入。本质上是经过反射的方式直接注入到field。这是我日常开发中看的最多也是最熟悉的一种方式,同时,也正是 Spring 团队所不推荐的方式。好比:
@Autowired
private Svc svc;
复制代码
经过对应变量的setXXX()
方法以及在方法上面使用注解,来完成依赖注入。好比:
private Helper helper;
@Autowired
public void setHelper(Helper helper) {
this.helper = helper;
}
复制代码
注:在
Spring 4.3
及之后的版本中,setter 上面的@Autowired
注解是能够不写的。
将各个必需的依赖所有放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。好比:
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
修饰的变量)
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 这样推荐的理由,首先是基于构造方法注入,
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Spring 团队提倡使用基于构造方法的注入,由于这样一方面能够将依赖注入到一个不可变的变量中 (注:final
修饰的变量),另外一方面也能够保证这些变量的值不会是 null。此外,通过构造方法完成依赖注入的组件 (注:好比各个 service
),在被调用时能够保证它们都彻底准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法一般表明着出现了代码异味,这个类可能承担了过多的责任。
而对于基于 setter 的注入,他们是这么说的:
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。若是使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优势是,这个依赖能够很方便的被改变或者从新注入。
以上就是本文的全部内容,但愿阅读本文以后能让你对Spring 的依赖注入有更深的理解。
若是本文有帮助到你,但愿能点个赞,这是对个人最大动力🤝🤝🤗🤗。