spring @Primary 详解

50 阅读4分钟

在 Spring 的日常开发中,我们经常会遇到一个接口有多个实现类的情况。当我们尝试使用 @Autowired 注入该接口时,Spring 容器会因为不知道该选哪一个实现类而抛出 NoUniqueBeanDefinitionException

为了解决这个问题,Spring 提供了两个核心注解:@Qualifier@Primary

今天这篇文章,我们就来深度聊聊 @Primary 这个注解的作用、使用场景以及它与 @Qualifier 的区别。


1. 什么是 @Primary?

@Primary 的字面意思是“主要的、首选的”。

在 Spring 容器中,如果同一个类型的 Bean 有多个实例,且在注入时没有明确指定注入哪一个,那么标注了 @Primary 的 Bean 就会被优先选择。

  • 注解定义位置:可以放在类上(配合 @Component 等),也可以放在方法上(配合 @Bean)。
  • 核心作用:打破依赖注入时的“平局”局面,指定默认候选项。

2. 场景演示:没有 @Primary 会发生什么?

假设我们有一个发送短信的接口 SmsService,以及两个实现类:

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 中注入这个接口:

@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:“如果没说要哪个,就默认用我吧!”

@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 配置多个数据源或特定组件时,可以指定一个主数据源。

@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!如果觉得有帮助,欢迎点赞关注。🚀


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