1. 引言
在本文中,我們將探討如何在 Spring Boot 中動態地注入具有多個實現的接口,以及相關的實現方式和使用場景。 這是一個強大的特性,允許開發者在應用程序中動態地注入不同實現的接口。
2. 默認行為
通常,當我們有多個接口實現並且嘗試將該接口自動注入到組件中時,我們會收到一個錯誤信息:“需要一個單一的 Bean,但 X 個 Bean 被找到”。 原因是簡單:Spring 不知道我們希望在組件中看到哪個實現。 幸運的是,Spring 提供了多種工具來更具體地指定。
3. 介紹限定符 (Qualifiers)
使用@Qualifier註解,我們可以指定在多個候選 Bean 中,我們想要進行自動注入的 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;
}
此外,我們還可以將實現自動注入到集合、映射或數組中。當使用映射時,格式通常為 ,其中鍵是 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 到一個集合中,無論這些 Bean 是否帶有限定符或參數名稱,只要它們處於激活狀態。
它會忽略帶有 @Profile 註解的 Bean,只要這些 Bean 與當前 Profile 不匹配。同樣,Spring 只會包含帶有 @Conditional 註解的 Bean,如果條件滿足時(更多細節請參見下一部分)。
7. 高級控制
Spring 允許我們對選擇哪些候選者進行自動裝配擁有額外的控制權。
為了更精確地指定豆子成為自動裝配的候選者,我們可以使用 @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
}
}在本示例中,WindowsFileService 將僅成為自動注入的候選者,如果 matches() 在 OnWindowsCondition 中返回 true。
對於非集合自動注入,我們應謹慎使用 @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 不起作用。
8. 結論
在本文中,我們探討了 Spring 在管理接口多項式實現期間提供的工具。這些工具和技術使設計 Spring Boot 應用程序時能夠採用更動態的方法。然而,就像任何工具一樣,我們應該確保其必要性,因為不當使用可能會引入錯誤並使長期支持變得複雜。