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 小时前
Bun 都用 AI + Rust 重写了,咋不顺便把 Node.js 的 API 全兼容了?
前端·后端
XovH1 小时前
第49篇 k8s之服务网格入门:Istio 简介
后端
无毁的湖光Al1 小时前
高可用之路-闲聊监控指标的局限
后端
AskHarries1 小时前
第一个项目应该做多大
后端
Lcos1 小时前
三分钟吃透 Radix Tree:Hertz 路由插入全拆解
后端
Gopher_HBo1 小时前
Go语言学习笔记(六)面向对象
后端
San813_LDD1 小时前
[后端开发]GET/POST_带参/不带参
前端·后端·计算机网络·json
白露与泡影1 小时前
SEATA:Server 到 Golang Client 全链路走读
开发语言·后端·golang
小江的记录本2 小时前
【Spring全家桶】Spring Cloud 2023.0.x:配置中心:Nacos Config、Apollo(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·python·spring·spring cloud·面试