1. 程式人生 > >spring為什麽推薦使用構造器註入

spring為什麽推薦使用構造器註入

發現 framework spring容器 docs 責任 ref microsoft field cep

一、前言

?  項目中遇到一個問題:項目啟動完成前,在A類中註入B類,並調用B類的某個方法。

  那麽調用B類的這個方法寫在哪裏呢,我選擇寫到構造器裏,但是構造器先於Spring註入執行,那麽執行構造器時,註入B類肯定為null,於是選擇了構造器註入,解決問題

  執行順序為:靜態變量或靜態語句塊–>實例變量或初始化語句塊–>構造方法–>Spring註入

二、常見的三種註入方式

? 筆者為了方便起見就只是用註解的方式註入(現在也很少使用xml了吧)

2.1 註解註入

@Controller
public class FooController {
  @Autowired
  private FooService fooService;
  
  //簡單的使用例子,下同
  public List<Foo> listFoo() {
      return fooService.list();
  }
}

  

這種註入方式應該是筆者目前為止開發中見到的最常見的註入方式。原因很簡單:

  1. 註入方式非常簡單:加入要註入的字段,附上@Autowired,即可完成。
  2. 使得整體代碼簡潔明了,看起來美觀大方。

2.2 構造器註入

@Controller
public class FooController {
  
  private final FooService fooService;
  
  @Autowired
  public FooController(FooService fooService) {
      this.fooService = fooService;
  }
  
  //使用方式上同,略
}

  

? 在Spring4.x版本中推薦的註入方式就是這種,相較於上面的註解註入方式而言,就顯得有點難看,特別是當註入的依賴很多(5個以上)的時候,就會明顯的發現代碼顯得很臃腫。對於從註解註入轉過來+有強迫癥的園友 來說,簡直可以說是無法忍受。對於這一點我們後面再來討論,別急。

2.3 setter註入

@Controller
public class FooController {
  
  private FooService fooService;
  
  //使用方式上同,略
  @Autowired
  public void setFooService(FooService fooService) {
      this.fooService = fooService;
  }
}

  

? 在Spring3.x剛推出的時候,推薦使用註入的就是這種,筆者現在也基本沒看到過這種註解方式,寫起來麻煩,當初推薦Spring自然也有他的道理,這裏我們引用一下Spring當時的原話:

The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.

Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.

? 咳咳,簡單的翻譯一下就是:構造器註入參數太多了,顯得很笨重,另外setter的方式能用讓類在之後重新配置或者重新註入

? 那麽後面為什麽又換成構造器註入了呢?(餵餵餵,Spring你前一大版本還貶低構造器註入,後面就立刻捧人家了不好吧,不過能用於承認自己的錯誤,才是真正令人稱贊的地方吧 (??????)??)

三、構造器註入的好處

? 先來看看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.

? 咳咳,再來簡單的翻譯一下:這個構造器註入的方式啊,能夠保證註入的組件不可變,並且確保需要的依賴不為空。此外,構造器註入的依賴總是能夠在返回客戶端(組件)代碼的時候保證完全初始化的狀態

下面來簡單的解釋一下:

  • 依賴不可變:其實說的就是final關鍵字,這裏不再多解釋了。不明白的園友可以回去看看Java語法。
  • 依賴不為空(省去了我們對其檢查):當要實例化FooController的時候,由於自己實現了有參數的構造函數,所以不會調用默認構造函數,那麽就需要Spring容器傳入所需要的參數,所以就兩種情況:1、有該類型的參數->傳入,OK 。2:無該類型的參數->報錯。所以保證不會為空,Spring總不至於傳一個null進去吧 :-(
  • 完全初始化的狀態:這個可以跟上面的依賴不為空結合起來,向構造器傳參之前,要確保註入的內容不為空,那麽肯定要調用依賴組件的構造方法完成實例化。而在Java類加載實例化的過程中,構造方法是最後一步(之前如果有父類先初始化父類,然後自己的成員變量,最後才是構造方法,這裏不詳細展開。)。所以返回來的都是初始化之後的狀態。

等等,比較完了setter註入與構造器註入的優缺點,你還沒用說使用field註入與構造器的比較呢!那麽我們再回頭看一看使用最多的field註入方式:

//承接上面field註入的代碼,假如客戶端代碼使用下面的調用(或者再Junit測試中使用)
//這裏只是模擬一下,正常來說我們只會暴露接口給客戶端,不會暴露實現。
FooController fooController = new FooController();
fooController.listFoo(); // -> NullPointerException

如果使用field註入,缺點顯而易見,對於IOC容器以外的環境,除了使用反射來提供它需要的依賴之外,無法復用該實現類。而且將一直是個潛在的隱患,因為你不調用將一直無法發現NPE的存在。

還值得一提另外一點是:使用field註入可能會導致循環依賴,即A裏面註入B,B裏面又註入A:

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}

  

如果使用構造器註入,在spring項目啟動的時候,就會拋出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?從而提醒你避免循環依賴,如果是field註入的話,啟動的時候不會報錯,在使用那個bean的時候才會報錯

四、答疑

? 好了,相信已經園友們知道了構造器註入的好處,那麽回到了在前面提到的問題:

Q1:跟3.x裏說的一樣,我要是有大量的依賴要註入,構造方法不會顯得很臃腫嗎?

對於這個問題,說明你的類當中有太多的責任,那麽你要好好想一想是不是自己違反了類的單一性職責原則,從而導致有這麽多的依賴要註入。

Q2:是不是其他的註入方式都不適合用了呢?

當然不是,存在即是合理!setter的方式既然一開始被Spring推薦肯定是有它的道理,像之前提到的setter的方式能用讓類在之後重新配置或者重新註入,就是其優點之一。除此之外,如果一個依賴有多種實現方式,我們可以使用@Qualifier,在構造方法裏選擇對應的名字註入,也可以使用field或者setter的方式來手動配置要註入的實現。

五、總結

? 使用構造器註入的好處:

  1. 保證依賴不可變(final關鍵字)
  2. 保證依賴不為空(省去了我們對其檢查)
  3. 保證返回客戶端(調用)的代碼的時候是完全初始化的狀態
  4. 避免了循環依賴
  5. 提升了代碼的可復用性

另外,當有一個依賴有多個實現的使用,推薦使用field註入或者setter註入的方式來指定註入的類型。這是spring官方博客對setter註入方式和構造器註入的比較。謝謝各位園友觀看,如果有描述不對的地方歡迎指正,與大家共同進步!

spring為什麽推薦使用構造器註入