1. 概述

在本篇教程中,我们将深入探讨 Spring 框架从 3.0 版本引入的 @Primary 注解。

✅ 核心作用一句话总结:当 Spring 容器中存在多个同类型的 Bean 时,使用 @Primary 可以指定其中一个为“首选 Bean”,在自动注入时优先选用它

这听起来简单,但在实际开发中非常实用。比如你有两个数据源、两种策略实现,又不想每次注入都写 @Qualifier,那 @Primary 就是你的救星。

下面我们通过几个典型场景,带你彻底搞懂它的用法和适用时机。


2. 为什么需要 @Primary?

在 Spring 中,我们通常依赖类型自动装配(byType),但一旦容器里有多个相同类型的 Bean,就会出问题。

⚠️ 典型报错如下:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
Expected single matching bean but found 2: JohnEmployee, TonyEmployee

来看一个具体例子:

@Configuration
public class Config {

    @Bean
    public Employee JohnEmployee() {
        return new Employee("John");
    }

    @Bean
    public Employee TonyEmployee() {
        return new Employee("Tony");
    }
}

上面注册了两个 Employee 类型的 Bean。此时如果你尝试用 @Autowired 注入 Employee,Spring 就懵了:到底该选哪个?

常规解法是配合 @Qualifier("beanName") 明确指定:

@Autowired
@Qualifier("TonyEmployee")
private Employee employee;

但这有个前提:你得在注入点手动指定。可如果是在配置类中定义 Bean,无法使用 @Qualifier 来“选”另一个 Bean —— 因为还没到注入阶段。

这时候 @Primary 就派上用场了:它允许我们在定义 Bean 时就声明“我是默认选项”,从而避免歧义。


3. 在 @Bean 上使用 @Primary

最常见的一种用法就是在 @Bean 方法上标记 @Primary,告诉 Spring:“这个 Bean 是同类中的默认选择”。

修改配置类如下:

@Configuration
public class Config {

    @Bean
    public Employee JohnEmployee() {
        return new Employee("John");
    }

    @Bean
    @Primary
    public Employee TonyEmployee() {
        return new Employee("Tony");
    }
}

✅ 关键点:

  • TonyEmployee() 被标记为 @Primary
  • 当通过类型获取 Employee Bean 时,Spring 会优先返回 TonyEmployee

验证一下:

AnnotationConfigApplicationContext context = 
    new AnnotationConfigApplicationContext(Config.class);

Employee employee = context.getBean(Employee.class);
System.out.println(employee);

输出结果:

Employee{name='Tony'}

✔️ 成功注入了被标记为 @PrimaryTonyEmployee 实例。

也就是说,只要没有显式使用 @Qualifier 覆盖,默认就会选 @Primary 标记的那个 Bean


4. 在 @Component 上使用 @Primary

除了在配置类中使用,@Primary 也可以直接标注在组件类上,比如 @Component@Service@Repository 等。

假设我们有一个管理角色的接口:

public interface Manager {
    String getManagerName();
}

然后有两个实现类:

❌ 普通实现类(非首选)

@Component
public class DepartmentManager implements Manager {
    @Override
    public String getManagerName() {
        return "Department manager";
    }
}

✅ 首选实现类(标记 @Primary)

@Component
@Primary
public class GeneralManager implements Manager {
    @Override
    public String getManagerName() {
        return "General manager";
    }
}

注意:这两个类都被 Spring 扫描到,且都实现了 Manager 接口。

为了让组件扫描生效,别忘了启用 @ComponentScan

@Configuration
@ComponentScan(basePackages = "com.example.primary")
public class Config {
}

接着写一个服务类来消费这个依赖:

@Service
public class ManagerService {

    @Autowired
    private Manager manager;

    public Manager getManager() {
        return manager;
    }
}

虽然 DepartmentManagerGeneralManager 都符合 Manager 类型,但由于 GeneralManager 被标记了 @Primary,所以自动注入的是它。

测试代码:

AnnotationConfigApplicationContext context = 
    new AnnotationConfigApplicationContext(Config.class);

ManagerService service = context.getBean(ManagerService.class);
Manager manager = service.getManager();
System.out.println(manager.getManagerName());

输出:

General manager

✅ 成功注入了首选 Bean。


5. 总结

场景 是否推荐使用 @Primary
多个同类型 Bean,想设一个默认项 ✅ 强烈推荐
所有注入都需要精确控制 ❌ 改用 @Qualifier 更安全
第三方库提供多个实现,你想替换默认行为 ✅ 可自定义配置类 + @Primary 覆盖

📌 关键要点回顾:

  • @Primary 解决的是“多选一”时的默认选择问题
  • 它作用于 Bean 定义阶段,比 @Qualifier 更“前置”
  • 可用于 @Bean@Component 及其衍生注解(如 @Service
  • 若同时存在多个 @Primary,仍会抛出 NoUniqueBeanDefinitionException —— 所以一个类型只能有一个“首选”
  • @Primary 的优先级高于单纯的类型匹配,但低于 @Qualifier

💡 小贴士:在实际项目中,如果你封装了一个通用组件(比如默认消息处理器),可以用 @Primary 让它成为默认实现,同时允许用户通过 @Qualifier 替换为自定义实现,灵活又友好。


完整示例代码已托管至 GitHub:

👉 https://github.com/spring-example/primary-demo


原始标题:Spring @Primary Annotation | Baeldung