Spring官方為什么建議構(gòu)造器注入?
點擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)”

前言
本章的內(nèi)容主要是想探討我們在進(jìn)行 Spring 開發(fā)過程當(dāng)中,關(guān)于依賴注入的幾個知識點。感興趣的讀者可以先看下以下問題:
@Autowired,@Resource,@Inject三個注解的區(qū)別當(dāng)你在使用
@Autowired時,是否有出現(xiàn)過Field injection is not recommended的警告?你知道這是為什么嗎?Spring 依賴注入有哪幾種方式?官方是怎么建議使用的呢?
如果你對上述問題都了解,那我個人覺得你的開發(fā)經(jīng)驗應(yīng)該是不錯的??。
下面我們就依次對上述問題進(jìn)行解答,并且總結(jié)知識點。
@Autowired,@Resource,@Inject 三個注解的區(qū)別
Spring 支持使用@Autowired, @Resource, @Inject 三個注解進(jìn)行依賴注入。下面來介紹一下這三個注解有什么區(qū)別。
@Autowired
@Autowired為Spring 框架提供的注解,需要導(dǎo)入包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進(jìn)行匹配如果有
@Qualifier注解,則按照@Qualifier指定的name進(jìn)行匹配查找name為svcA的bean如果沒有,則按照變量名進(jìn)行匹配
查找name為svc的bean匹配不到,則報錯。(
@Autowired(required=false),如果設(shè)置required為false(默認(rèn)為true),則注入失敗時不會拋出異常)
@Inject
在 Spring 的環(huán)境下,@Inject和@Autowired 是相同的,因為它們的依賴注入都是使用AutowiredAnnotationBeanPostProcessor來處理的。

@Inject是 JSR-330 定義的規(guī)范,如果使用這種方式,切換到Guice也是可以的。
Guice 是 google 開源的輕量級 DI 框架
如果硬要說兩個的區(qū)別,首先@Inject是 Java EE 包里的,在 SE 環(huán)境需要單獨(dú)引入。另一個區(qū)別在于@Autowired可以設(shè)置required=false而@Inject并沒有這個屬性。
@Resource
@Resource是 JSR-250 定義的注解。Spring 在 CommonAnnotationBeanPostProcessor實現(xiàn)了對JSR-250的注解的處理,其中就包括@Resource。

@Resource有兩個重要的屬性:name和type,而Spring 將@Resource注解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。
裝配順序:
如果同時指定了
name和type,則從 Spring 上下文中找到唯一匹配的 bean 進(jìn)行裝配,找不到則拋出異常。如果指定了
name,則從上下文中查找名稱(id)匹配的 bean 進(jìn)行裝配,找不到則拋出異常。如果指定了
type,則從上下文中找到類型匹配的唯一 bean 進(jìn)行裝配,找不到或是找到多個,都會拋出異常。如果既沒有指定
name,又沒有指定type,則默認(rèn)按照byName方式進(jìn)行裝配;如果沒有匹配,按照byType進(jìn)行裝配。
Field injection is not recommended
在使用 IDEA 進(jìn)行 Spring 開發(fā)的時候,當(dāng)你在字段上面使用@Autowired注解的時候,你會發(fā)現(xiàn) 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 開發(fā)團(tuán)隊建議:在你的Spring Bean 永遠(yuǎn)使用基于constructor 的方式進(jìn)行依賴注入。對于必須的依賴,永遠(yuǎn)使用斷言來確認(rèn)。
比如如下代碼:
@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");
}
}
將光標(biāo)放到@Autowired處,使用Alt + Enter 快捷進(jìn)行修改之后,代碼就會變成基于 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 團(tuán)隊的建議,如果svc是必須的依賴,應(yīng)該使用Assert.notNull(svc, "svc must not be null")來確認(rèn)。
修正這個警告提示固然簡單,但是我覺得更重要是去理解為什么 Spring 團(tuán)隊會提出這樣的建議?直接使用這種基于 field 的注入方式有什么問題?
首先我們需要知道,Spring 中有這么3種依賴注入的方式:
基于 field 注入(屬性注入)
基于 setter 注入
基于 constructor 注入(構(gòu)造器注入)
1. 基于 field 注入
所謂基于 field 注入,就是在bean的變量上使用注解進(jìn)行依賴注入。本質(zhì)上是通過反射的方式直接注入到 field。這是我平常開發(fā)中看的最多也是最熟悉的一種方式,同時,也正是 Spring 團(tuán)隊所不推薦的方式。比如:
@Autowired
private Svc svc;
2. 基于 setter 方法注入
通過對應(yīng)變量的setXXX()方法以及在方法上面使用注解,來完成依賴注入。比如:
private Helper helper;
@Autowired
public void setHelper(Helper helper) {
this.helper = helper;
}
注:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不寫的。
3. 基于 constructor 注入
將各個必需的依賴全部放在帶有注解構(gòu)造方法的參數(shù)中,并在構(gòu)造方法中完成對應(yīng)變量的初始化,這種方式,就是基于構(gòu)造方法的注入。比如:
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
this.svc = svc;
}
在 Spring 4.3 及以后的版本中,如果這個類只有一個構(gòu)造方法,那么這個構(gòu)造方法上面也可以不寫 @Autowired 注解。
基于 field 注入的好處
正如你所見,這種方式非常的簡潔,代碼看起來很簡單,通俗易懂。你的類可以專注于業(yè)務(wù)而不被依賴注入所污染。你只需要把@Autowired扔到變量之上就好了,不需要特殊的構(gòu)造器或者set方法,依賴注入容器會提供你所需的依賴。
基于 field 注入的壞處
成也蕭何敗也蕭何
基于 field 注入雖然簡單,但是卻會引發(fā)很多的問題。這些問題在我平常開發(fā)閱讀項目代碼的時候就經(jīng)常遇見。
容易違背了單一職責(zé)原則 使用這種基于 field 注入的方式,添加依賴是很簡單的,就算你的類中有十幾個依賴你可能都覺得沒有什么問題,普通的開發(fā)者很可能會無意識地給一個類添加很多的依賴。但是當(dāng)使用構(gòu)造器方式注入,到了某個特定的點,構(gòu)造器中的參數(shù)變得太多以至于很明顯地發(fā)現(xiàn) something is wrong。擁有太多的依賴通常意味著你的類要承擔(dān)更多的責(zé)任,明顯違背了單一職責(zé)原則(SRP:Single responsibility principle)。 依賴注入與容器本身耦合 依賴注入框架的核心思想之一就是受容器管理的類不應(yīng)該去依賴容器所使用的依賴。換句話說,這個類應(yīng)該是一個簡單的 POJO(Plain Ordinary Java Object)能夠被單獨(dú)實例化并且你也能為它提供它所需的依賴。 這個問題具體可以表現(xiàn)在: 你的類不能繞過反射(例如單元測試的時候)進(jìn)行實例化,必須通過依賴容器才能實例化,這更像是集成測試 你的類和依賴容器強(qiáng)耦合,不能在容器外使用 不能使用屬性注入的方式構(gòu)建不可變對象( final修飾的變量)
Spring 開發(fā)團(tuán)隊的建議
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.
強(qiáng)制依賴就用構(gòu)造器方式 可選、可變的依賴就用 setter 注入 當(dāng)然你可以在同一個類中使用這兩種方法。構(gòu)造器注入更適合強(qiáng)制性的注入旨在不變性,Setter 注入更適合可變性的注入。
final 修飾的變量),另一方面也可以保證這些變量的值不會是 null。此外,經(jīng)過構(gòu)造方法完成依賴注入的組件 (注:比如各個 service),在被調(diào)用時可以保證它們都完全準(zhǔn)備好了。與此同時,從代碼質(zhì)量的角度來看,一個巨大的構(gòu)造方法通常代表著出現(xiàn)了代碼異味,這個類可能承擔(dān)了過多的責(zé)任。小結(jié)
參考
Setter-based dependency injection Field Dependency Injection Considered Harmful IDEA 警告 Field injection is not recommended
轉(zhuǎn)自:Richard_Yi
鏈接:https://juejin.cn/post/6844904056230690824
后臺回復(fù) 學(xué)習(xí)資料 領(lǐng)取學(xué)習(xí)視頻
如有收獲,點個在看,誠摯感謝
