spring @Primary 详解

在 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. 注意事项

  1. 唯一性 :对于同一类型的 Bean,最多只能有一个标注为 @Primary。如果标注了多个,Spring 依然会因为不知道选哪个而报错。
  2. 配合 @Bean 使用 :在 Java 配置类中,@Primary 必须和 @Bean 写在一起。
  3. 不要滥用 :如果你发现大部分地方都需要手动指定不同的 Bean,那么 @Qualifier 可能比 @Primary 更合适。@Primary 应该只用于那个最通用、最默认的实现。

总结

@Primary 是 Spring 依赖注入中一个非常实用的"软约束"。它不强制要求消费端修改代码,而是通过在提供端声明"默认优先"的方式,优雅地解决了多实例冲突问题。

  • 如果你想定义一个默认行为,请用 @Primary
  • 如果你想在注入点精确控制,请用 @Qualifier

希望这篇文章能帮你彻底理解 @Primary!如果觉得有帮助,欢迎点赞关注。🚀


作者:你的名字 发布于:掘金

相关推荐
星栈独行1 小时前
Node 框架怎么选?Express、Koa、Egg、NestJS 场景化选型指南
后端·程序人生·node.js
你为她披上外套时我正站在窗外1 小时前
拆解 siwi-download:Rust 异步下载器是怎么炼成的
后端
苍何1 小时前
WAIC深度体验:能跨端使用的 Agent 才是好 Agent!
后端
程序员清风2 小时前
推荐几个我常听的AI播客!
java·后端·面试
JavaGuide2 小时前
Kimi K3 实战:全栈项目、Java 项目改造与 3A 游戏 Demo
后端·ai编程
wuqingshun3141592 小时前
如何理解Spring Boot中的starter?
java·spring boot·后端
AskHarries2 小时前
PayPal 接入避坑
后端
谭光志2 小时前
深入浅出 RAG:用一个可运行的 Demo 讲透完整链路
前端·后端·ai编程
wuqingshun3141592 小时前
SpringBoot是如何实现自动配置的
java·spring boot·后端
道友可好2 小时前
前端工程师的 AI 时代生存指南
前端·人工智能·后端