Spring 中 @Primary 注解的原理是什么?

发布时间:2025-05-19 01:33:47 作者:益华网络 来源:undefined 浏览量(0) 点赞(0)
摘要:来源:江南一点雨 1. 问题分析 当我们使用 Spring 的时候,有时候会遇到下面这种情况。 假设我有 A、B 两个类,在 A 中注入 B,如下:@ComponentpublicclassA {&nbs

来源:江南一点雨

1. 问题分析

当我们使用 Spring 的时候,有时候会遇到下面这种情况。

假设我有 A、B 两个类,在 A 中注入 B,如下:

@Componentpublic class A 

{

    @Autowired

    B b;

}

至于 B,则在配置类中存在多个实例:

@Configuration@ComponentScanpublic class JavaConfig 

{

    @Bean("b1"

)

    b1() 

{

        return new

 B();

    }

    @Bean("b2"

)

    b2() 

{

        return new

 B();

    }

}

这样的项目启动之后,必然会抛出如下异常:

当然,对于这样的问题,相信有经验的同学都知道该怎么解决:

可以使用 @Resource 注解,使用该注解时指定具体的 Bean 名称即可。在 @Autowired 注解之上,再多加一个 @Qualifier("b1") 注解,通过该注解去指定要加载的 Bean 名称。@Componentpublic class A 

{

    @Autowired    @Qualifier("b1"

)

    B b;

}

在多个 B 对象的某一个之上,添加 @Primary 注解,表示当存在重复的 B 对象时,优先使用哪一个。@Configuration@ComponentScanpublic class JavaConfig 

{

    @Bean("b1"

)

    @Primary    b1() 

{

        return new

 B();

    }

    @Bean("b2"

)

    b2() 

{

        return new

 B();

    }

}

除了这三个,还有没有其他办法呢?必须有!!!在 Spring 中 @Qualifier 注解还能这么用? 一文中,松哥还和大家扩展了 @Qualifier 注解的其他用法,感兴趣的小伙伴不要错过哦。

这里三个方法,其中 @Resource 是 JSR 中提供的注解,我这里先不展开,松哥后面专门再来和大家聊 @Resource 注解的注入原理。今天我主要是想和小伙伴们分享一下后面两种方案的实现原理。

2. 源码解析

本文基于前面@Autowired 到底是怎么把变量注入进来的?一文展开,所以如果还没看过改文章的小伙伴,建议先去阅读一下,这有助于更好的理解本文。

2.1 doResolveDependency

在@Autowired 到底是怎么把变量注入进来的?的 3.3 小节中,我们提到,给 A 注入 B 的时候,会调用到 doResolveDependency 方法,我们再来看下该方法:

DefaultListableBeanFactory#doResolveDependency:

@Nullablepublic Object doResolveDependency

(DependencyDescriptor descriptor, @Nullable String beanName,

  @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter)
 throws BeansException 

{

     //...

  Map matchingBeans = findAutowireCandidates(beanName, type, descriptor);

  if

 (matchingBeans.isEmpty()) {

   if

 (isRequired(descriptor)) {

    raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);

   }

   return null

;

  }

  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 didnt even look for collection beans).     return null

;

    }

   }

   instanceCandidate = matchingBeans.get(autowiredBeanName);

  //...

}

在这个方法中,首先调用了 findAutowireCandidates 方法去找到所有满足条件的 Class。Map 中的 key 就是 Bean 的名称,value 则是一个 Class,此时还没有实例化。

如果我们是通过 @Qualifier 注解来解决问题的,那么问题就在 findAutowireCandidates 方法中被解决了。这个在前面的文章 Spring 中 @Qualifier 注解还能这么用? 中已经和小伙伴们聊过了。

如果 @Qualifier 注解没把问题解决掉,就会导致最终查询到的 matchingBeans 的数量大于 1,那么就会进入到接下来的 if 环节中,通过 determineAutowireCandidate 方法进一步确定到底使用哪一个 Bean,@Primary 注解的处理,就在该方法中完成。

2.2 determineAutowireCandidate

DefaultListableBeanFactory#determineAutowireCandidate

@Nullableprotected 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 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 方法去确定最佳候选 Bean,这个方法本质上就是通过 @Primary 注解找到最佳 BeanName。如果第一步没有找到最佳 BeanName,那么接下来会调用 determineHighestPriorityCandidate 方法去查找最佳 Bean,该方法本质上是通过查找 JSR-330 中的 @Priority 注解,来确定 Bean 的优先级。如果前两步都没找到合适的 BeanName,那么接下来这个 for 循环则是通过 Bean 的名称进行匹配了,即 A 类中变量的名称和目标 Bean 的名称是否匹配,如果能匹配上,那也可以。这也就是我么常说的 @Autowired 注解先按照类型去匹配,如果类型匹配不上,就会按照名称去匹配。

上面大致介绍了这个方法的执行思路,接下来我们就来看一下执行细节。

2.2.1 determinePrimaryCandidate@Nullableprotected String determinePrimaryCandidate(Map<String, Object> candidates, Class<?> requiredType) 

{

 String primaryBeanName = null

;

 for

 (Map.Entry 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();

 }

 return (getParentBeanFactory() instanceof

 DefaultListableBeanFactory parent &&

   parent.isPrimary(transformedBeanName, beanInstance));

}

我们来看下这个方法的执行逻辑。

参数 candidates 中保存了所有符合条件的 BeanDefinition,参数 key 就是 Bean 的名称,Value 则是对应的 BeanDefinition。现在就去遍历 candidates,在遍历的时候,调用 isPrimary 方法去判断这个 BeanDefinition 上是否含有 @Primary 注解,isPrimary 方法的逻辑比较简单,我就不啰嗦了,该方法中涉及到 getMergedLocalBeanDefinition 方法去父容器中查找两个细节,这个松哥在之前的文章中也都和大家聊过了(Spring BeanDefinition:父子关系解密、Spring 中的父子容器是咋回事?)。

在查找的过程中,如果有满足条件的 BeanName,则赋值给 primaryBeanName 变量然后返回,如果存在多个满足条件的 BeanName,那就抛出 NoUniqueBeanDefinitionException 异常。

2.2.2 determineHighestPriorityCandidate

要理解 determineHighestPriorityCandidate 方法,得先了解 @Priority 注解的用法。考虑到有的小伙伴可能还不熟悉 @Priority 注解,我这里也跟大家稍微说两句。

@Priority 注解作用有点类似于 @Order,可以用来指定一个 Bean 的优先级,这是 JSR 中提供的注解,所以如果想使用这个注解,需要先添加依赖:

<dependency>    <groupId>jakarta.annotation</groupId>    <artifactId>jakarta.annotation-api</artifactId>    <version>2.1.1</version></dependency>

然后在类上添加该注解,像下面这样:

public interface IBService 

{

}

@Component@Priority(100

)

public class BServiceImpl1 implements IBService

{

}

@Component@Priority(101

)

public class BServiceImpl2 implements IBService

{

}

@Priority 注解中的数字表示优先级,数字越大优先级越小。将来在 A 中注入 IBService 时,就会优先查找优先级高的 Bean。虽然 @Priority 注解可以加在类上,也可以加在方法上,但是在具体实践中,加在方法上这个注解并不会生效,只能加在类上面。至于原因,大家看完接下来的源码分析就懂了。

现在我们再来看下 determineHighestPriorityCandidate 方法:

@Nullableprotected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) 

{

 String highestPriorityBeanName = null

;

 Integer highestPriority = null

;

 for

 (Map.Entry entry : candidates.entrySet()) {

  String candidateBeanName = entry.getKey();

  Object beanInstance = entry.getValue();

  if (beanInstance != null

) {

   Integer candidatePriority = getPriority(beanInstance);

   if (candidatePriority != null

) {

    if (highestPriorityBeanName != null

) {

     if

 (candidatePriority.equals(highestPriority)) {

      throw new

 NoUniqueBeanDefinitionException(requiredType, candidates.size(),

        "Multiple beans found with the same priority ("

 + highestPriority +

        ") among candidates: "

 + candidates.keySet());

     }

     else if

 (candidatePriority < highestPriority) {

      highestPriorityBeanName = candidateBeanName;

      highestPriority = candidatePriority;

     }

    }

    else

 {

     highestPriorityBeanName = candidateBeanName;

     highestPriority = candidatePriority;

    }

   }

  }

 }

 return

 highestPriorityBeanName;

}

determineHighestPriorityCandidate 方法的整体处理思路跟 determinePrimaryCandidate 方法特别像,不同的是 determinePrimaryCandidate 方法处理的是 @Primary 注解,而 determineHighestPriorityCandidate 方法处理的是 @Priority 注解。

determineHighestPriorityCandidate 方法也是遍历 candidates,然后调用 getPriority 方法获取到具体的优先级的值。然后根据这个具体的数字选定一个合适的 beanName 返回,如果存在多个优先级相同的 bean,那么就会抛出 NoUniqueBeanDefinitionException 异常。

最后再来看下 getPriority 方法,几经辗转之后,该方法会调用到 AnnotationAwareOrderComparator#getPriority 方法:

@Override@Nullablepublic Integer getPriority(Object obj) 

{

 if (obj instanceof

 Class  clazz) {

  return

 OrderUtils.getPriority(clazz);

 }

 Integer priority = OrderUtils.getPriority(obj.getClass());

 if (priority == null  && obj instanceof

 DecoratingProxy decoratingProxy) {

  return

 getPriority(decoratingProxy.getDecoratedClass());

 }

 return

 priority;

}

可以看到,这里最终就是调用 OrderUtils.getPriority 方法去查找参数 clazz 上的 @Priority 注解,并找到注解上对应的值返回。OrderUtils.getPriority 在执行的时候,参数时 clazz,即只会查找 clazz 上的 注解,并不会查找方法上的注解,因此前面我说 @Priority 注解要加在类上才有效。

2.2.3 按名称匹配

最后我们再来看下按照名字去匹配的逻辑:

// Fallbackfor

 (Map.Entry entry : candidates.entrySet()) {

 String candidateName = entry.getKey();

 Object beanInstance = entry.getValue();

 if ((beanInstance != null && this

.resolvableDependencies.containsValue(beanInstance)) ||

   matchesBeanName(candidateName, descriptor.getDependencyName())) {

  return

 candidateName;

 }

}

protected boolean matchesBeanName(String beanName, @Nullable String candidateName) 

{

 return (candidateName != null

 &&

   (candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));

}

可以看到,这里也是遍历 candidates 集合,然后调用 matchesBeanName 方法,在该方法中,会去判断候选的 BeanName 和需要注入的变量名(descriptor.getDependencyName())是否相等,如果相等,就直接返回即可。即下面这种代码不需要额外的注解是可以运行不会报错的:

@Componentpublic class AService 

{

    @Autowired

    B b1;

}

@Configuration@ComponentScanpublic class JavaConfig 

{

    @Bean    public B b1() 

{

        return new

 B();

    }

    @Bean    b2() 

{

        return new

 B();

    }

}

3. 小结

好啦,经过上面的分析,现在小伙伴们明白了 @Primary 注解的完整处理逻辑了吧~本文结合@Autowired 到底是怎么把变量注入进来的? 和 Spring 中 @Qualifier 注解还能这么用? 一起食用效果更好哦!

二维码

扫一扫,关注我们

声明:本文由【益华网络】编辑上传发布,转载此文章须经作者同意,并请附上出处【益华网络】及本页链接。如内容、图片有任何版权问题,请联系我们进行处理。

感兴趣吗?

欢迎联系我们,我们愿意为您解答任何有关网站疑难问题!

您身边的【网站建设专家】

搜索千万次不如咨询1次

主营项目:网站建设,手机网站,响应式网站,SEO优化,小程序开发,公众号系统,软件开发等

立即咨询 15368564009
在线客服
嘿,我来帮您!