Field injection is not recommended(Spring IOC不再推薦使用屬性注入)
Field injection is not recommended(Spring IOC不再推薦使用屬性注入)
1. 說明
最近公司升級框架,由原來的spring framerwork 3.0
升級到5.0
,然後寫程式碼的時候突然發現idea在屬性注入的**@Autowired**註解上給出警告提示,就像下面這樣的,也挺懵逼的,畢竟這麼寫也很多年了。
Field injection is not recommended
查閱了相關文件瞭解了一下,原來這個提示是spring framerwork 4.0
以後開始出現的,spring 4.0開始就不推薦使用屬性注入,改為推薦構造器注入和setter注入。
下面將展示了spring框架可以使用的不同型別的依賴注入,以及每種依賴注入的適用情況。
2. 依賴注入的型別
儘管針對spring framerwork 5.1.3
的文件只定義了兩種主要的依賴注入型別,但實際上有三種;
- 基於建構函式的依賴注入
- 基於setter的依賴注入
- 基於欄位的依賴注入
其中基於欄位的依賴注入
被廣泛使用,但是idea或者其他靜態程式碼分析工具會給出提示資訊,不推薦使用。
甚至可以在一些Spring官方指南中看到這種注入方法,儘管在文件中並不推薦:
2.1 基於建構函式的依賴注入
在基於建構函式的依賴注入中,類建構函式被標註為**@Autowired**,幷包含了許多與要注入的物件相關的引數。
@Component
public class ConstructorBasedInjection {
private final InjectedBean injectedBean;
@Autowired
public ConstructorBasedInjection(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
然後在spring官方文件中,@Autowired註解也是可以省去的。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
基於建構函式注入的主要優點是可以將需要注入的欄位宣告為final, 使得它們會在類例項化期間被初始化,這對於所需的依賴項很方便。
2.2 基於Setter的依賴注入
在基於setter的依賴注入中,setter方法被標註為**@Autowired**。一旦使用無引數建構函式或無引數靜態工廠方法例項化Bean,為了注入Bean的依賴項,Spring容器將呼叫這些setter方法。
@Component
public class SetterBasedInjection {
private InjectedBean injectedBean;
@Autowired
public void setInjectedBean(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
和基於構造器的依賴注入一樣,在官方文件中,基於Setter的依賴注入中的**@Autowired**也可以省去。(見更新1)
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
2.3 基於屬性的依賴注入
在基於屬性的依賴注入中,欄位/屬性被標註為**@Autowired**。一旦類被例項化,Spring容器將設定這些欄位。
@Component
public class FieldBasedInjection {
@Autowired
private InjectedBean injectedBean;
}
正如所看到的,這是依賴注入最乾淨的方法,因為它避免了新增樣板程式碼,並且不需要宣告類的建構函式。程式碼看起來很乾淨簡潔,但是正如程式碼檢查器已經向我們暗示的那樣,這種方法有一些缺點。
3. 基於欄位的依賴注入缺陷
3.1 不允許宣告不可變域
基於欄位的依賴注入在宣告為final/immutable的欄位上不起作用,因為這些欄位必須在類例項化時例項化。宣告不可變依賴項的惟一方法是使用基於構造器的依賴注入。
3.2 容易違反單一職責設計原則
在面向物件的程式設計中,五大設計原則SOLID)被廣泛應用,(國內一般為六大設計原則),用以提高程式碼的重用性,可讀性,可靠性和可維護性
S在SOLID中代表單一職責原則,即即一個類應該只負責一項職責,這個類提供的所有服務都應該只為它負責的職責服務。
使用基於欄位的依賴注入,高頻使用的類隨著時間的推移,我們會在類中逐漸新增越來越多的依賴項,我們用著很爽,很容易忽略類中的依賴已經太多了。但是如果使用基於建構函式的依賴注入,隨著越來越多的依賴項被新增到類中,建構函式會變得越來越大,我們一眼就可以察覺到哪裡不對勁。
有一個有超過10個引數的建構函式是一個明顯的訊號,表明類已經轉變一個大而全的功能合集,需要將類分割成更小、更容易維護的塊。
因此,儘管屬性注入並不是破壞單一責任原則的直接原因,但它隱藏了訊號,使我們很容易忽略這些訊號。
3.3 與依賴注入容器緊密耦合
使用基於欄位的依賴注入的主要原因是為了避免getter和setter的樣板程式碼或為類建立建構函式。最後,這意味著設定這些欄位的唯一方法是通過Spring容器例項化類並使用反射注入它們,否則欄位將保持null。
依賴注入設計模式將類依賴項的建立與類本身分離開來,並將此責任轉移到類注入容器,從而允許程式設計解耦,並遵循單一職責和依賴項倒置原則(同樣可靠)。因此,通過自動裝配(autowiring)欄位來實現的類的解耦,最終會因為再次與類注入容器(在本例中是Spring)耦合而丟失,從而使類在Spring容器之外變得無用。
這意味著,如果您想在應用程式容器之外使用您的類,例如用於單元測試,您將被迫使用Spring容器來例項化您的類,因為沒有其他可能的方法(除了反射)來設定自動裝配欄位。
3.4 隱藏依賴關係
在使用依賴注入時,受影響的類應該使用公共介面清楚地公開這些依賴項,方法是在建構函式中公開所需的依賴項,或者使用方法(setter)公開可選的依賴項。當使用基於欄位的依賴注入時,實質上是將這些依賴對外隱藏了。
4. 總結
我們已經看到,基於欄位的注入應該儘可能地避免,因為它有許多缺點,無論它看起來多麼優雅。推薦的方法是使用基於建構函式和基於setter的依賴注入。對於必需的依賴,建議使用基於建構函式的注入,設定它們為不可變的,並防止它們為null。對於可選的依賴項,建議使用基於sett的注入。
5. 參考文件
6. 更新問題(2021.2.1)
1.關於setter-based DI@Autowire
註解可以省略的問題
前文有些想當然了,@Autowire
具體用例可以見這裡1.9.2. Using@Autowired
大意是:
從Spring Framework 4.3開始,如果目標bean只定義了一個建構函式,那麼這樣的建構函式上不再需要@Autowired註釋。但是,如果有幾個建構函式可用,那麼至少必須用@Autowired註釋一個建構函式,以便指示容器使用哪個建構函式。
2. mock問題
最近在使用中發現一個坑,改成構造器注入以後,如果要mock,簡直就是個大坑,如果公司內要mock,改造需謹慎, 。。。
3.關於三種方式選擇那種。
首先基於欄位的依賴注入不推薦
其次,spring團隊首推使用基於構造器注入的方式
最後,如果有特殊需要,可以基於setter方法和基於構造器注入一起使用,但是兩者側重點不同,具體可以見下面的原文
https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-dependency-resolution
翻譯(懶癌發作,簡單的有道翻譯,看看意思即可):
基於建構函式還是基於setter的注入?
因為你可以混合使用基於建構函式和基於setter的DI,對於強制依賴使用建構函式,對於可選依賴使用setter方法或配置方法是一個很好的經驗法則。注意,在setter方法上使用@Required註釋可以使屬性成為必需的依賴項;但是,使用程式設計驗證引數的建構函式注入更可取。
Spring團隊通常提倡建構函式注入,因為它允許您將應用程式元件實現為不可變物件,並確保所需的依賴項不為空。而且,建構函式注入的元件總是以完全初始化的狀態返回給客戶端(呼叫)程式碼。附帶說明一下,大量的建構函式引數是一種糟糕的程式碼味道,這意味著類可能有太多的責任,應該進行重構,以更好地處理關注點的適當分離。
Setter注入應該主要只用於可選的依賴項,這些依賴項可以在類中分配合理的預設值。否則,必須在程式碼使用依賴項的任何地方執行非空檢查。setter注入的一個好處是,setter方法使該類的物件易於重新配置或稍後重新注入。因此,通過JMX mbean進行管理是setter注入的一個引人注目的用例。
使用對特定類最有意義的DI樣式。有時,當處理您沒有原始碼的第三方類時,選擇是為您做出的。例如,如果第三方類沒有公開任何setter方法,那麼建構函式注入可能是DI唯一可用的形式。