环境:Spring Boot3.2.5
1. 简介
本篇文章,我们将探讨学习在 Spring Boot 中自动装配时当一个接口有多个实现时如何选择正确的实现类进行注入。这是一项强大的功能,允许开发人员将接口的不同实现动态注入应用程序中。
通常,当我们有多个接口实现并尝试将该接口自动注入到组件中时,会遇到一个错误——“需要单个bean,但找到了X个”。原因很简单:Spring不知道我们想要在该组件中使用哪个实现类。不过,Spring对于这种情况还是提供了多种方式来让我们更具体地指定所需要的类。
接下来,我将详细的介绍6种实现方式。
2. 实战案例
2.1 准备环境
首先,定义一个接口DAO
public interface DAO<T> {
List<T> queryList() ;
}
接下来,定义2个实现类
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个接口实现,接下来的案例中我们将围绕这2个进行讲解。
2.2 使用@Qualifier注解
使用@Qualifier注解,我们可以在多个实现类中指定要自动装配哪个bean。我们可以将其应用于组件本身,为其提供一个自定义的限定符名称,如下示例:
@Repository
@Qualifier("mysqlDAO-1")
public class MySQLDAO implements DAO<User> {}
@Repository
public class OracleDAO implements DAO<User> {}
这里2个实现使用@Repository注册为bean,其中MySQLDAO实现使用了@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参数并没有添加@Qualifier注解,它也能正确的注入,这是因为默认情况Spring会按照名称进行装配(确保定义bean的名称与你这参数名称一致)。
2.3 使用@Primary注解
此外,我们还可以用 @Primary 来注解其中一个实现。如果有多个候选实现,且按参数名或限定符自动装配不适用,Spring 将使用该实现:
@Repository
@Primary
public class OracleDAO implements DAO<User> {}
当我们经常使用其中一种实现时,此种方式就会非常有用,这非常有助于避免出现 如下的错误信息:
图片
在这种情况下如果你还需要使用其它的实现,你可以通过ApplicationContext#getBean手动方式来获取。
2.4 使用@Profile注解
可以使用Spring的配置文件(profiles)来决定要自动装配哪个组件。如上示例,我们可以让OracleDAO实现仅在生产环境配置文件(prod profile)下激活,而MySQLDAO仅在开发环境配置文件(dev profile)下激活,如下示例:
@Repository
@Profile("dev")
public class MySQLDAO implements DAO<User> {}
@Repository
@Profile("prod")
public class OracleDAO implements DAO<User> {}
具体哪个会生效,就看你当前环境配置的spring.profiles.active属性是dev还是prod了。
2.5 所有实现装配到集合中
我们可以将特定类型的所有可用 Bean 注入到一个集合中,如下示例:
@Component
public class CompDAO {
private final List<DAO> daos ;
public CompDAO(List<DAO> daos) {
this.daos = daos ;
}
}
此外,我们还可以将实现自动装配到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不会考虑限定符或参数名称。它会忽略注释为 @Profile 的、与当前配置文件不匹配的 Bean。
2.6 使用@Priority注解
我们还可以使用jakarta.annotation.Priority注解,为每一个实现类定义优先级,该注解有一个Integer类型的参数,值越小优先级越高,当存在多个类型的优先级越高的会被优先注入,如下示例:
@Repository
@Priority(2)
public class OracleDAO implements DAO<Date> {}
@Repository
@Priority(1)
public class MySQLDAO implements DAO<Date> {}
如上示例,由于MySQLDAO设置优先级最小,所以注入的将是MySQLDAO。
2.7 自定义Condition
为了更精确地确定哪个 bean 成为自动装配的候选者,我们可以使用 @Conditional 注解对它们进行标注。@Conditional 注解应该有一个参数,该参数是一个实现了 Condition 接口(它是一个函数式接口)的类,如下示例:
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属性设置为mysql(matches方法返回true)时才会创建MySQLDAO。