@Primary 是做什么的?
简单来说,@Primary
的作用是:当 IoC 容器中存在多个相同类型的 Bean 时,告诉 Spring 在进行依赖注入时,优先选择被 @Primary
标记的那个。
让我们用一个比喻来解释:默认收货地址
想象一下你在购物网站(比如淘宝或京东)的账户里保存了多个收货地址:
- 地址A:你家的地址
- 地址B:你公司的地址
- 地址C:你父母家的地址
当你下单时,网站需要知道把货送到哪里。如果你不指定,系统就会很困惑:“我到底该用哪个地址?” 这时就会出错。
@Primary
就相当于你把“地址A(你家)”设置为了“默认收货地址”。
- 默认情况:你下单时,网站会自动选用你家的地址。
- 特殊情况:如果你这次想把东西寄到公司,你就需要在下单时明确地选择“地址B”。
在 Spring 的世界里:
- 收货地址 -> Bean 的实现
- 下单 -> 进行
@Autowired
依赖注入 - 系统困惑 -> Spring 抛出
NoUniqueBeanDefinitionException
异常 - 设置默认地址 (
@Primary
) -> 标记一个 Bean 为首选 - 明确选择其他地址 (
@Qualifier
) -> 使用@Qualifier
注解指定想要的 Bean
问题场景:没有 @Primary
会发生什么?
假设我们有一个通知服务,它既可以通过邮件发送,也可以通过短信发送。
1. 定义接口
public interface NotificationService {void sendNotification(String message);
}
2. 两个实现类
@Service // 这是一个Bean
public class EmailNotificationService implements NotificationService {@Overridepublic void sendNotification(String message) {System.out.println("通过邮件发送通知: " + message);}
}@Service // 这也是一个Bean,和上面那个类型相同
public class SmsNotificationService implements NotificationService {@Overridepublic void sendNotification(String message) {System.out.println("通过短信发送通知: " + message);}
}
3. 在其他地方注入
@Service
public class OrderService {private final NotificationService notificationService;@Autowired // Spring在这里犯了难!public OrderService(NotificationService notificationService) {this.notificationService = notificationService;}public void placeOrder() {// ...下单逻辑...notificationService.sendNotification("您的订单已成功下单!");}
}
当你启动这个应用时,Spring 会立即报错:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.myapp.NotificationService' available: expected single matching bean but found 2: emailNotificationService,smsNotificationService
错误原因:Spring 在尝试为 OrderService
注入 NotificationService
时,发现容器里有两个符合条件的 Bean(EmailNotificationService
和 SmsNotificationService
)。它不知道该选哪一个,所以只能抛出异常。
解决方案:使用 @Primary
现在,假设我们系统的主要通知方式是邮件。我们可以把 EmailNotificationService
标记为首选。
@Service
@Primary // <<-- 加上这个注解
public class EmailNotificationService implements NotificationService {// ...
}
其他代码完全不变。现在再次启动应用,一切正常!
当 OrderService
需要 NotificationService
时,Spring 再次发现有两个候选者。但这次,它看到 EmailNotificationService
身上有 @Primary
标记,于是它毫不犹豫地选择了这个 Bean 进行注入。
@Primary
vs @Qualifier
@Qualifier
是解决同样问题的另一种方式,但它们的侧重点不同。
特性 | @Primary | @Qualifier("beanName") |
---|---|---|
解决方式 | “默认”机制。在众多候选者中指定一个为首选。 | “指定”机制。明确告诉 Spring 我要哪一个。 |
注解位置 | 写在 Bean 的定义类上。(@Service , @Component 旁边) | 写在 注入点上。(@Autowired 旁边) |
影响范围 | 全局性。所有不明确指定的注入都会默认使用它。 | 局部性。只对当前这一个注入点有效。 |
代码侵入性 | 对使用者透明,使用者不需要知道有多个实现。 | 使用者必须知道具体 Bean 的名字,并修改注入代码。 |
什么时候用哪个?
-
使用
@Primary
:当你的实现中有一个是明确的、常用的、默认的选项时。比如,90% 的情况都用邮件通知,只有个别特殊场景用短信。这时将邮件服务设为@Primary
是最优雅的。 -
使用
@Qualifier
:当多个实现没有主次之分,它们是平等的,使用者必须根据业务场景显式地选择一个时。或者,当你需要在一个特定地方覆盖@Primary
的默认行为时。
覆盖 @Primary
的例子:
@Service
public class HighPriorityAlertService {private final NotificationService notificationService;@Autowired// 即使EmailService是@Primary,我这里也明确指定要用SmsServicepublic HighPriorityAlertService(@Qualifier("smsNotificationService") NotificationService notificationService) {this.notificationService = notificationService;}public void sendAlert() {notificationService.sendNotification("!!系统紧急警报!!");}
}
在这个例子中,即使 EmailNotificationService
是首选,HighPriorityAlertService
依然通过 @Qualifier
精确地注入了 SmsNotificationService
。
总结
@Primary
是一个强大而优雅的工具,用于解决依赖注入中的歧义性问题。它通过在 Bean 定义端设立一个“默认选项”,使得注入端的代码可以保持干净和通用,是实现“约定优于配置”思想的一个绝佳体现。