在Spring Boot中具有多個(gè)實(shí)現(xiàn)的接口正確注入組件的六種方式
環(huán)境:Spring Boot3.2.5
1. 簡介
本篇文章,我們將探討學(xué)習(xí)在 Spring Boot 中自動裝配時(shí)當(dāng)一個(gè)接口有多個(gè)實(shí)現(xiàn)時(shí)如何選擇正確的實(shí)現(xiàn)類進(jìn)行注入。這是一項(xiàng)強(qiáng)大的功能,允許開發(fā)人員將接口的不同實(shí)現(xiàn)動態(tài)注入應(yīng)用程序中。
通常,當(dāng)我們有多個(gè)接口實(shí)現(xiàn)并嘗試將該接口自動注入到組件中時(shí),會遇到一個(gè)錯(cuò)誤——“需要單個(gè)bean,但找到了X個(gè)”。原因很簡單:Spring不知道我們想要在該組件中使用哪個(gè)實(shí)現(xiàn)類。不過,Spring對于這種情況還是提供了多種方式來讓我們更具體地指定所需要的類。
接下來,我將詳細(xì)的介紹6種實(shí)現(xiàn)方式。
2. 實(shí)戰(zhàn)案例
2.1 準(zhǔn)備環(huán)境
首先,定義一個(gè)接口DAO
public interface DAO<T> {
List<T> queryList() ;
}接下來,定義2個(gè)實(shí)現(xiàn)類
public class MySQLDAO implements DAO<User> {
@Override
public List<User> queryList()
// TODO
}
}
public class OracleDAO implements DAO<User> {
@Override
public List<Date> queryList() {
// TODO
}
}這里針對DAO接口,定義了2個(gè)接口實(shí)現(xiàn),接下來的案例中我們將圍繞這2個(gè)進(jìn)行講解。
2.2 使用@Qualifier注解
使用@Qualifier注解,我們可以在多個(gè)實(shí)現(xiàn)類中指定要自動裝配哪個(gè)bean。我們可以將其應(yīng)用于組件本身,為其提供一個(gè)自定義的限定符名稱,如下示例:
@Repository
@Qualifier("mysqlDAO-1")
public class MySQLDAO implements DAO<User> {}
@Repository
public class OracleDAO implements DAO<User> {}這里2個(gè)實(shí)現(xiàn)使用@Repository注冊為bean,其中MySQLDAO實(shí)現(xiàn)使用了@Qualifier限定了bean名稱。接下來,在注入時(shí),我們就可以使用@Qualifier來限定注入的bean
@Component
public class CompDAO {
private final DAO dao1 ;
private final DAO dao2 ;
public CompDAO(
@Qualifier("mysqlDAO-1") DAO dao1,
DAO oracleDAO) {
this.dao1 = dao1 ;
this.dao2 = dao2 ;
}
}需要注意的是,這里的第二個(gè)DAO參數(shù)并沒有添加@Qualifier注解,它也能正確的注入,這是因?yàn)槟J(rèn)情況Spring會按照名稱進(jìn)行裝配(確保定義bean的名稱與你這參數(shù)名稱一致)。
2.3 使用@Primary注解
此外,我們還可以用 @Primary 來注解其中一個(gè)實(shí)現(xiàn)。如果有多個(gè)候選實(shí)現(xiàn),且按參數(shù)名或限定符自動裝配不適用,Spring 將使用該實(shí)現(xiàn):
@Repository
@Primary
public class OracleDAO implements DAO<User> {}當(dāng)我們經(jīng)常使用其中一種實(shí)現(xiàn)時(shí),此種方式就會非常有用,這非常有助于避免出現(xiàn) 如下的錯(cuò)誤信息:
圖片
在這種情況下如果你還需要使用其它的實(shí)現(xiàn),你可以通過ApplicationContext#getBean手動方式來獲取。
2.4 使用@Profile注解
可以使用Spring的配置文件(profiles)來決定要自動裝配哪個(gè)組件。如上示例,我們可以讓OracleDAO實(shí)現(xiàn)僅在生產(chǎn)環(huán)境配置文件(prod profile)下激活,而MySQLDAO僅在開發(fā)環(huán)境配置文件(dev profile)下激活,如下示例:
@Repository
@Profile("dev")
public class MySQLDAO implements DAO<User> {}
@Repository
@Profile("prod")
public class OracleDAO implements DAO<User> {}具體哪個(gè)會生效,就看你當(dāng)前環(huán)境配置的spring.profiles.active屬性是dev還是prod了。
2.5 所有實(shí)現(xiàn)裝配到集合中
我們可以將特定類型的所有可用 Bean 注入到一個(gè)集合中,如下示例:
@Component
public class CompDAO {
private final List<DAO> daos ;
public CompDAO(List<DAO> daos) {
this.daos = daos ;
}
}此外,我們還可以將實(shí)現(xiàn)自動裝配到Set、Map或Array中。使用Map時(shí),格式通常是 Map<String, DAO>,其中鍵是 Bean 的名稱,值是 Bean 實(shí)例本身,如下示例:
@Component
public class CompDAO {
private final Map<String, DAO> daos ;
public CompDAO(Map<String, DAO> daos) {
this.daos = daos ;
}
public void use() {
MySQLDAO mdao = this.daos.get("mySQLDAO") ;
OracleDAO odao = this.daos.get("oracleDAO") ;
// TODO
}
}注意:此時(shí)Spring不會考慮限定符或參數(shù)名稱。它會忽略注釋為 @Profile 的、與當(dāng)前配置文件不匹配的 Bean。
2.6 使用@Priority注解
我們還可以使用jakarta.annotation.Priority注解,為每一個(gè)實(shí)現(xiàn)類定義優(yōu)先級,該注解有一個(gè)Integer類型的參數(shù),值越小優(yōu)先級越高,當(dāng)存在多個(gè)類型的優(yōu)先級越高的會被優(yōu)先注入,如下示例:
@Repository
@Priority(2)
public class OracleDAO implements DAO<Date> {}
@Repository
@Priority(1)
public class MySQLDAO implements DAO<Date> {}如上示例,由于MySQLDAO設(shè)置優(yōu)先級最小,所以注入的將是MySQLDAO。
2.7 自定義Condition
為了更精確地確定哪個(gè) bean 成為自動裝配的候選者,我們可以使用 @Conditional 注解對它們進(jìn)行標(biāo)注。@Conditional 注解應(yīng)該有一個(gè)參數(shù),該參數(shù)是一個(gè)實(shí)現(xiàn)了 Condition 接口(它是一個(gè)函數(shù)式接口)的類,如下示例:
public class PackCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment()
.getProperty("pack.active")
.toLowerCase()
.equals("mysql") ;
}
}使用
@Repository
@Conditional(PackCondition.class)
public class MySQLDAO implements DAO<User> {
// ...
}在上面的示例中,只有配置文件的pack.active屬性設(shè)置為mysql(matches方法返回true)時(shí)才會創(chuàng)建MySQLDAO。

































