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