簡體   English   中英

為什么在 CDI 中使用構造函數而不是 setter 注入?

[英]Why use constructor over setter injection in CDI?

我在這里找不到任何合理的答案,所以我希望它不是重復的。 那么為什么我更喜歡 setter 或構造函數注入而不是簡單的

@Inject
MyBean bean;

如果您需要在類初始化期間對注入的 bean 執行某些操作,我會使用構造函數注入,例如

public void MyBean(@Inject OtherBean bean) {
    doSomeInit(bean);
    //I don't need to use @PostConstruct now
}

但是,它和@PostConstruct方法幾乎一樣,我根本沒有得到 setter 注入,這不就是 Spring 和其他 DI 框架之后的遺物嗎?

即使在非 CDI 環境中,構造函數和屬性注入也使您可以輕松地初始化對象,例如單元測試。

在非 CDI 環境中,您仍然可以通過傳遞構造函數 arg 來簡單地使用該對象。

OtherBean b = ....;
new MyBean(b);

如果你只是使用字段注入,你通常必須使用反射來訪問字段,因為字段通常是私有的。

如果您使用屬性注入,您還可以在 setter 中編寫代碼。 例如,驗證代碼或您清除內部緩存,這些緩存保存從 setter 修改的屬性派生的值。 您想要做什么取決於您的實施需求。

Setter 與構造函數注入

在面向對象編程中,對象在構造后必須處於有效狀態,並且每次方法調用都會將狀態更改為另一個有效狀態。

對於 setter 注入,這意味着您可能需要更復雜的狀態處理,因為對象在構造后應該處於有效狀態,即使尚未調用 setter。 因此,即使未設置屬性,對象也必須處於有效狀態。 例如,通過使用默認值或空對象

如果對象的存在與屬性之間存在依賴關系,則該屬性應該是構造函數參數。 這也將使代碼更干凈,因為如果您使用構造函數參數,您將記錄依賴項是必要的。

所以不要寫這樣的類

public class CustomerDaoImpl implements CustomerDao {
 
  private DataSource dataSource;
 
  public Customer findById(String id){
     checkDataSource();

     Connection con = dataSource.getConnection();
     ...
     return customer;
  }

  private void checkDataSource(){
     if(this.dataSource == null){
         throw new IllegalStateException("dataSource is not set");
     }
  }

 
  public void setDataSource(DataSource dataSource){
     this.dataSource = dataSource;
  }
 
}

你應該使用構造函數注入

public class CustomerDaoImpl implements CustomerDao {
 
  private DataSource dataSource;
 
  public CustomerDaoImpl(DataSource dataSource){
      if(dataSource == null){
        throw new IllegalArgumentException("Parameter dataSource must not be null");
     }
     this.dataSource = dataSource;
  }
 
  public Customer findById(String id) {    
      Customer customer = null;
     // We can be sure that the dataSource is not null
     Connection con = dataSource.getConnection();
     ...
     return customer;
  }
}

我的結論

  • 為每個可選依賴項使用屬性
  • 對每個強制依賴使用構造函數參數

PS:我的博客pojos和java bean的區別更詳細地解釋了我的結論。

編輯

Spring 還建議使用構造函數注入,正如我在 spring 文檔的基於 Setter 的依賴注入部分中找到的那樣。

Spring 團隊通常提倡構造函數注入,因為它可以讓您將應用程序組件實現為不可變對象,並確保所需的依賴項不為空。 此外,構造函數注入的組件總是以完全初始化的狀態返回給客戶端(調用)代碼。 作為旁注,大量的構造函數參數是一種糟糕的代碼味道,這意味着該類可能有太多的責任,應該重構以更好地解決適當的關注點分離問題。

Setter 注入應該主要僅用於可以在類中分配合理默認值的可選依賴項。 否則,必須在代碼使用依賴項的任何地方執行非空檢查。 setter 注入的一個好處是 setter 方法使該類的對象可以在以后重新配置或重新注入。 因此,通過 JMX MBean 進行管理是 setter 注入的一個引人注目的用例。

考慮單元測試時,構造函數注入也是一種更好的方法,因為調用構造函數比設置私有 (@Autowired) 字段更容易。

使用 CDI 時,沒有任何理由使用構造函數或 setter 注入。 如問題中所述,您添加了一個@PostConstruct方法,否則將在構造函數中完成。

其他人可能會說在單元測試中需要使用反射來注入字段,但事實並非如此; 模擬庫和其他測試工具會為您做到這一點。

最后,構造函數注入允許字段為final ,但這並不是@Inject注釋字段(不能為final )的真正缺點。 注釋的存在,再加上沒有任何明確設置字段的代碼,應該清楚地表明它只能由容器(或測試工具)設置。 實際上,沒有人會重新分配注入的字段。

構造函數和 setter 注入在過去是有意義的,當時開發人員通常必須手動實例化並將依賴項注入到測試對象中。 如今,技術已經發展,場注入是一個更好的選擇。

接受的答案很好,但是它並沒有歸功於構造函數注入的主要優勢 - 類不變性,這有助於實現線程安全、狀態安全和更好的類可讀性。

假設您有一個具有依賴項的類,並且所有這些依賴項都作為構造函數參數提供,那么您可以知道該對象將永遠不會存在於依賴項無效的狀態中。 這些依賴項不需要設置器(只要它們是私有的),因此對象被實例化為完整狀態或根本不實例化。

不可變對象更有可能在多線程應用程序中表現良好。 盡管該類仍然需要在內部成為線程安全的,但您不必擔心外部客戶端協調對對象的訪問。

當然,這僅在某些情況下才有用。 Setter 注入非常適合部分依賴,例如我們在一個類中有 3 個屬性和 3 個 arg 構造函數和 setter 方法。 在這種情況下,如果您只想為一個屬性傳遞信息,則只能通過 setter 方法。 對於測試目的非常有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM