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'}
✔️ 成功注入了被标记为 @Primary
的 TonyEmployee
实例。
也就是说,只要没有显式使用 @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;
}
}
虽然 DepartmentManager
和 GeneralManager
都符合 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: