當我們在使用依賴注入的時候,通常有三種方式:
1.通過構造器來注入;
2.通過setter方法來注入;
3.通過filed變量來注入;
那么他們有什么區別嗎?應該選擇哪種方式更好?
代碼示例:
Constructor
1 private DependencyA dependencyA; 2 private DependencyB dependencyB; 3 private DependencyC dependencyC; 4 5 @Autowired 6 public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) { 7 this.dependencyA = dependencyA; 8 this.dependencyB = dependencyB; 9 this.dependencyC = dependencyC; 10 }
Setter
1 private DependencyA dependencyA; 2 private DependencyB dependencyB; 3 private DependencyC dependencyC; 4 5 @Autowired 6 public void setDependencyA(DependencyA dependencyA) { 7 this.dependencyA = dependencyA; 8 } 9 10 @Autowired 11 public void setDependencyB(DependencyB dependencyB) { 12 this.dependencyB = dependencyB; 13 } 14 15 @Autowired 16 public void setDependencyC(DependencyC dependencyC) { 17 this.dependencyC = dependencyC; 18 }
Field
1 @Autowired 2 private DependencyA dependencyA; 3 4 @Autowired 5 private DependencyB dependencyB; 6 7 @Autowired 8 private DependencyC dependencyC;
三種方式的區別小結:
1.基於constructor的注入,會固定依賴注入的順序;該方式不允許我們創建bean對象之間的循環依賴關系,這種限制其實是一種利用構造器來注入的益處 - 當你甚至沒有注意到使用setter注入的時候,Spring能解決循環依賴的問題;
2.基於setter的注入,只有當對象是需要被注入的時候它才會幫助我們注入依賴,而不是在初始化的時候就注入;另一方面如果你使用基於constructor注入,CGLIB不能創建一個代理,迫使你使用基於接口的代理或虛擬的無參數構造函數。
3.相信很多同學都選擇使用直接在成員變量上寫上注解來注入,正如我們所見,這種方式看起來非常好,精短,可讀性高,不需要多余的代碼,也方便維護;
缺點:
1.當我們利用constructor來注入的時候,比較明顯的一個缺點就是:假如我們需要注入的對象特別多的時候,我們的構造器就會顯得非常的冗余、不好看,非常影響美觀和可讀性,維護起來也較為困難;
2.當我們選擇setter方法來注入的時候,我們不能將對象設為final的;
3.當我們在field變量上來實現注入的時候
a.這樣不符合JavaBean的規范,而且很有可能引起空指針;
b.同時也不能將對象標為final的;
c.類與DI容器高度耦合,我們不能在外部使用它;
d.類不通過反射不能被實例化(例如單元測試中),你需要用DI容器去實例化它,這更像集成測試;
... etc.
來自Spring官方文檔的建議:
在Spring 3.x 中,Spring團隊建議我們使用setter來注入:

大致是說大量的構造器參數會顯得非常笨重,尤其是當屬性是可選的時候。setter方法可以使類的對象在后來重新配置或者重新注入。提供所有的依賴意味着對象總是返回一個完全初始化狀態的client客戶端(調用)。缺點是對象變得不那么適合重新配置和重新注入。
而在Spring 4.x 中,Spring團隊不再建議我們使用setter來注入,改為了constructor:

Spring團隊通常建議使用構造器來注入,因為它允許一個應用程序組件實現為不可變對象,並確保所需的依賴項不是空。此外構造器注入組件總是返回一個完全初始化狀態的client客戶端(調用)。附注,大量的構造函數參數是一個糟糕的代碼習慣,看起來也很壞,這意味着類可能有太多的責任,應該被重構,以更好地解決適當的關注點分離。
setter方法只應該主要的用在可以在類中指定合理的默認值的可選的依賴關系。否則,用到依賴的所有地方都應該進行非空檢查。setter注入的一個好處是,setter方法使類的對象可以在之后重新配置或者重新注入。
(以上是本人的渣渣英語翻譯結合有道得來。。大佬看到請輕噴)
接下來插播一條Spring 4.3 的新特征:
在Spring 4.3 以后,如果我們的類中只有單個構造函數,那么Spring就會實現一個隱式的自動注入,上代碼:
之前:
1 @Service 2 public class FooService { 3 4 private final FooRepository repository; 5 6 @Autowired 7 public FooService(FooRepository repository) { 8 this.repository = repository 9 } 10 }
在Spring 4.3 之后:
1 @Service 2 public class FooService { 3 4 private final FooRepository repository; 5 6 public FooService(FooRepository repository) { 7 this.repository = repository 8 } 9 }
如我們所見,我去掉了構造器上的@Autowired注解,經測試后發現,程序能正常運行,repository的依賴也被成功注入了,當時感覺就很amazing。。有興趣的同學可以試試~
總結:
1.強制性的依賴性或者當目標不可變時,使用構造函數注入(應該說盡量都使用構造器來注入)
2.可選或多變的依賴使用setter注入(建議可以使用構造器結合setter的方式來注入)
3.在大多數的情況下避免field域注入(感覺大多數同學可能會有異議,畢竟這個方式寫起來非常簡便,但是它的弊端確實遠大於這些優點)
4.Spring 4.3+ 的同學可以試一試構造器的隱式注入,采用此方式注入后,使得我們的代碼更優雅,更獨立,減少了對Spring的依賴性。
ps: 轉載請標注出處謝謝。
