自動裝配具有多種實現的接口
一、簡介
在本文中,我們將探討在 Spring Boot 中自動組裝具有多個實作的介面、實作方法以及一些用例。這是一個強大的功能,允許開發人員動態地將介面的不同實作注入到應用程式中。
2. 預設行為
通常,當我們有多個介面實作並嘗試將該介面自動組裝到元件中時,我們會收到錯誤 – 「需要單一 bean,但找到了 X」。原因很簡單:Spring 不知道我們希望在該元件中看到哪個實作。幸運的是,Spring 提供了多種更具體的工具。
3. 預選賽介紹
使用@Qualifier註釋,我們指定要在多個候選者中自動組裝哪個bean。我們可以將其套用至元件本身,為其指定一個自訂限定符名稱:
@Service
@Qualifier("goodServiceA-custom-name")
public class GoodServiceA implements GoodService {
// implemantation
}
之後,我們用@Qualifier註解參數來指定我們想要的實作:
@Autowired
public SimpleQualifierController(
@Qualifier("goodServiceA-custom-name") GoodService niceServiceA,
@Qualifier("goodServiceB") GoodService niceServiceB,
GoodService goodServiceC
) {
this.goodServiceA = niceServiceA;
this.goodServiceB = niceServiceB;
this.goodServiceC = goodServiceC;
}
在上面的範例中,我們可以看到我們使用自訂限定詞來自動組裝GoodServiceA 。同時,對於GoodServiceB ,我們沒有自訂限定詞:
@Service
public class GoodServiceB implements GoodService {
// implementation
}
在本例中,我們透過類別名稱自動組裝組件。此類自動組裝的限定符應採用駝峰式大小寫,例如,如果類別名稱為“MyAwesomeClass”,則“myAwesomeClass “是有效的限定符。
上面程式碼中的第三個參數更有趣。我們甚至不需要用@Qualifier註解它,因為Spring 預設會嘗試透過參數名稱自動組裝元件,如果GoodServiceC存在,我們將避免錯誤:
@Service
public class GoodServiceC implements GoodService {
// implementation
}
4. 主要部件
此外,我們可以使用@Primary註釋其中一個實作。如果有多個候選者並且透過參數名稱或限定符自動組裝不適用,Spring 將使用此實作:
@Primary
@Service
public class GoodServiceC implements GoodService {
// implementation
}
當我們經常使用其中一種實作時,它很有用,有助於避免「需要單一 bean」錯誤。
5. 簡介
可以使用 Spring 設定檔來決定自動組裝哪個元件。例如,我們可能有一個具有兩種實作的FileStorage介面 - S3FileStorage和AzureFileStorage 。我們可以僅在prod設定檔上啟動S3FileStorage ,僅在dev設定檔上啟動AzureFileStorage 。
@Service
@Profile("dev")
public class AzureFileStorage implements FileStorage {
// implementation
}
@Service
@Profile("prod")
public class S3FileStorage implements FileStorage {
// implementation
}
6. 將實現自動裝配到集合中
Spring 允許我們將特定類型的所有可用 bean 注入到集合中。以下是我們如何將GoodService的所有實作自動組裝到清單中:
@Autowired
public SimpleCollectionController(List<GoodService> goodServices) {
this.goodServices = goodServices;
}
此外,我們可以將實作自動組裝到集合、映射或陣列中。使用映射時,格式通常為Map<String, GoodService> ,其中鍵是 bean 的名稱,值是 bean 實例本身:
@Autowired
public SimpleCollectionController(Map<String, GoodService> goodServiceMap) {
this.goodServiceMap = goodServiceMap;
}
public void printAllHellos() {
String messageA = goodServiceMap.get("goodServiceA").getHelloMessage();
String messageB = goodServiceMap.get("goodServiceB").getHelloMessage();
// print messages
}
重要提示:Spring 會將所有候選 bean 自動組裝到一個集合中,無論限定符或參數名稱為何,只要它們處於活動狀態即可。它會忽略使用@Profile註解且與目前設定檔不符的 bean。類似地,只有在滿足條件時,Spring 才會包含用@Conditional註釋的 bean(更多詳細資訊將在下一節中介紹)。
7. 高階控制
Spring 允許我們對選擇哪些候選人進行自動組裝進行額外的控制。
對於 bean 成為自動組裝候選者的更精確條件,我們可以使用@Conditional對其進行註解。它應該有一個帶有實現Condition的類別的參數(它是一個函數式介面)。例如,以下是檢查作業系統是否為 Windows 的Condition :
public class OnWindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}
以下是我們如何使用@Conditional註解我們的元件:
@Component
@Conditional(OnWindowsCondition.class)
public class WindowsFileService implements FileService {
@Override
public void readFile() {
// implementation
}
}
在此範例中,只有當OnWindowsCondition中的matches()傳回 true 時, WindowsFileService才會成為自動組裝的候選者。
我們應該小心非集合自動組裝的@Conditional註釋,因為符合條件的多個 bean 將導致錯誤。
此外,如果沒有找到候選人,我們也會收到錯誤訊息。因此,當將@Conditional與自動組裝整合時,設定可選注入是有意義的。這確保了應用程式在找不到合適的 bean 時仍然可以繼續運行而不會拋出錯誤。有兩種方法可以實現此目的:
@Autowired(required = false)
private GoodService goodService; // not very safe, we should check this for null
@Autowired
private Optional<GoodService> goodService; // safer way
當我們自動組裝到集合中時,我們可以使用@Order註釋指定元件的順序:
@Order(2)
public class GoodServiceA implements GoodService {
// implementation
}
@Order(1)
public class GoodServiceB implements GoodService {
// implementation
}
如果我們嘗試自動組裝List<GoodService> , GoodServiceB將被放置在GoodServiceA之前。重要提示:當我們自動組裝到Set中時, @Order不起作用。
八、結論
在本文中,我們討論了 Spring 提供的用於在自動組裝期間管理介面的多個實作的工具。這些工具和技術在設計 Spring Boot 應用程式時提供了更動態的方法。然而,就像每個工具一樣,我們應該確保它們的必要性,因為不小心使用可能會引入錯誤並使長期支援變得複雜。
與往常一樣,這些範例可以在 GitHub 上找到。