回答
如果我们想将某个 Bean 设置为默认 Bean,一般推荐使用 @Primary
。当应用中存在多个候选 Bean 的时候,Spring 会优先选择标记了 @Primary
的那个作为注入的默认 Bean。
详解
举个例子:
@Component
public class SkServiceA implements SkService {
// Implementation of MyService
}
@Component
@Primary
public class SkServiceB implements SkService {
// Implementation of MyService
}
在这里,如果某个地方需要注入 SkService ,Spring 会优先选择 @Primary
标注的 SkServiceB。但是,如果我们使用了 @Qualifier
指定了具体的 Bean 名称,那么即使有 @Primary
,Spring 也会注入指定的 Bean。
多候选 Bean 问题
Spring 容器在初始化时,如果某个类型有多个 Bean 的候选对象,如果我们不做任何处理,则系统会抛出如下异常:
NoUniqueBeanDefinitionException: No qualifying bean of type 'xxx' available: expected single matching bean but found x: [beanA, beanB]
为了避免这种情况的发生,Spring 提供了两种方式处理:
@Primary
:标记一个默认 Bean,当多个候选存在时优先选择。@Qualifier
:明确指定使用某个特定的 Bean。
在目标 Bean 明确的情况下,我们使用 @Qualifier
比较好。
在项目开发过程中,如果我们有一个实现类是主实现类,那么大明哥推荐将该实现类添加 @Primary
。同时,对于特殊的实现,使用 @Qualifier
来显示指定。在一些场景中,尤其是需要替换第三方库的时候,我们有时候需要覆盖默认实现,则我们就可以在自定义实现上添加 Primary
满足要求。
在实际使用中,我们需要根据业务需求搭配使用 @Primary
和 @Qualifier
,以确保依赖注入的明确性和灵活性。
关于 @Qualifier
请阅读面试题:Spring 中的 @Qualifier 注解的作用?
@Primary 的实现原理
我们直接看源码,DefaultListableBeanFactory#doResolveDependency()
:
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// 省略部分代码...
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
// 省略部分代码...
}
调用 findAutowireCandidates()
找到所有满足条件的 class,如果 matchingBeans.size() > 1
,则调用 determineAutowireCandidate()
:
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
// Fallback
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
matchesBeanName(candidateName, descriptor.getDependencyName())) {
return candidateName;
}
}
return null;
}
在这个方法里面,我们可以看到是一次调用三个方法 determinePrimaryCandidate()
、determineHighestPriorityCandidate()
、matchesBeanName()
来确认,不为空就返回:
determinePrimaryCandidate()
protected String determinePrimaryCandidate(Map<String, Object> candidates, Class<?> requiredType) {
String primaryBeanName = null;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
if (isPrimary(candidateBeanName, beanInstance)) {
if (primaryBeanName != null) {
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
if (candidateLocal && primaryLocal) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"more than one 'primary' bean found among candidates: " + candidates.keySet());
}
else if (candidateLocal) {
primaryBeanName = candidateBeanName;
}
}
else {
primaryBeanName = candidateBeanName;
}
}
}
return primaryBeanName;
}
protected boolean isPrimary(String beanName, Object beanInstance) {
String transformedBeanName = transformedBeanName(beanName);
if (containsBeanDefinition(transformedBeanName)) {
return getMergedLocalBeanDefinition(transformedBeanName).isPrimary();
}
BeanFactory parent = getParentBeanFactory();
return (parent instanceof DefaultListableBeanFactory &&
((DefaultListableBeanFactory) parent).isPrimary(transformedBeanName, beanInstance));
}
这个方法的逻辑比较简单,迭代调用 isPrimary()
来判断这个 BeanDefinition 上面是否含有 @Primary
。
determineHighestPriorityCandidate()
:该方法的本质是通过查找JSR-330
中的@Priority
,来确定 Bean 的优先级。matchesBeanName()
:该方法则是通过 BeanName 来进行匹配的。
这两个方法不是我们本篇面试题的重点,就不多介绍了。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。