在 Spring 的日常开发中,我们经常会遇到一个接口有多个实现类的情况。当我们尝试使用 @Autowired 注入该接口时,Spring 容器会因为不知道该选哪一个实现类而抛出 NoUniqueBeanDefinitionException。
为了解决这个问题,Spring 提供了两个核心注解:@Qualifier 和 @Primary。
今天这篇文章,我们就来深度聊聊 @Primary 这个注解的作用、使用场景以及它与 @Qualifier 的区别。
1. 什么是 @Primary?
@Primary 的字面意思是"主要的、首选的"。
在 Spring 容器中,如果同一个类型的 Bean 有多个实例,且在注入时没有明确指定注入哪一个,那么标注了 @Primary 的 Bean 就会被优先选择。
- 注解定义位置 :可以放在类上(配合
@Component等),也可以放在方法上(配合@Bean)。 - 核心作用:打破依赖注入时的"平局"局面,指定默认候选项。
2. 场景演示:没有 @Primary 会发生什么?
假设我们有一个发送短信的接口 SmsService,以及两个实现类:
java
public interface SmsService {
void send(String message);
}
@Service
public class AliyunSmsService implements SmsService {
@Override
public void send(String message) {
System.out.println("阿里云短信发送:" + message);
}
}
@Service
public class TencentSmsService implements SmsService {
@Override
public void send(String message) {
System.out.println("腾讯云短信发送:" + message);
}
}
现在我们在 Controller 中注入这个接口:
java
@RestController
public class SmsController {
@Autowired
private SmsService smsService; // 这里会报错
@GetMapping("/send")
public void send() {
smsService.send("Hello Spring!");
}
}
运行结果: 程序启动失败,报错如下:
NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.SmsService' available: expected single matching bean but found 2: aliyunSmsService,tencentSmsService
3. 使用 @Primary 解决问题
我们只需要在其中一个实现类上加上 @Primary 注解,告诉 Spring:"如果没说要哪个,就默认用我吧!"
java
@Primary // 标记为首选
@Service
public class AliyunSmsService implements SmsService {
@Override
public void send(String message) {
System.out.println("阿里云短信发送:" + message);
}
}
运行结果 : 程序成功启动,调用接口时,控制台会输出:阿里云短信发送:Hello Spring!。
4. @Primary vs @Qualifier
这是面试中经常被问到的点。两者都能解决多 Bean 注入冲突,但思路不同:
| 特性 | @Primary | @Qualifier |
|---|---|---|
| 侧重点 | 默认值。在提供端指定一个"备胎"中的老大。 | 精确查找。在消费端指定要哪一个。 |
| 位置 | 通常放在 Bean 的定义类或 @Bean 方法上。 |
通常放在 @Autowired 注入点上。 |
| 灵活性 | 较低。全局只能有一个 Primary(同类型下)。 | 较高。可以在不同地方注入不同的 Bean。 |
| 优先级 | 如果同时存在,@Qualifier 的优先级高于 @Primary。 |
显式指定的总是赢。 |
代码对比:
@Primary相当于:"我是默认选项。"@Qualifier("tencentSmsService")相当于:"我点名要腾讯云。" (即便阿里云上有@Primary,此时也会注入腾讯云)。
5. 常见应用场景
场景一:单元测试(Mock)
在测试环境下,你可能想用一个 Mock 实现来替代真实的 Service。你可以将 Mock 类标注为 @Primary,这样在测试代码注入时,会自动使用 Mock 对象。
场景二:框架默认实现与自定义扩展
这是 @Primary 最经典的使用场景。 很多 Spring Boot Starter 会提供一个默认的 Bean 供用户直接使用。如果你(开发者)定义了一个同类型的 Bean 并加上了 @Primary,Spring 就会优先使用你的 Bean,从而实现了"逻辑覆盖"或"自定义扩展"。
场景三:多配置环境
在使用 @Bean 配置多个数据源或特定组件时,可以指定一个主数据源。
java
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource mainDataSource() {
return new HikariDataSource(); // 主库
}
@Bean
public DataSource secondDataSource() {
return new HikariDataSource(); // 从库
}
}
6. 注意事项
- 唯一性 :对于同一类型的 Bean,最多只能有一个标注为
@Primary。如果标注了多个,Spring 依然会因为不知道选哪个而报错。 - 配合 @Bean 使用 :在 Java 配置类中,
@Primary必须和@Bean写在一起。 - 不要滥用 :如果你发现大部分地方都需要手动指定不同的 Bean,那么
@Qualifier可能比@Primary更合适。@Primary应该只用于那个最通用、最默认的实现。
总结
@Primary 是 Spring 依赖注入中一个非常实用的"软约束"。它不强制要求消费端修改代码,而是通过在提供端声明"默认优先"的方式,优雅地解决了多实例冲突问题。
- 如果你想定义一个默认行为,请用
@Primary。 - 如果你想在注入点精确控制,请用
@Qualifier。
希望这篇文章能帮你彻底理解 @Primary!如果觉得有帮助,欢迎点赞关注。🚀
作者:你的名字 发布于:掘金