前言
上篇文章我们总结了开发Spring应用时出现NoUniqueBeanDefinitionException问题的解决方案,那么这篇文章我们就来回答下存在多个相同类型的Bean时Primary注解标注的Bean怎么就能够成功注入,也可以借此从点到面领略下Spring的设计之美。
揭秘
Primary注解的本质
还记得我们在使用xml配置Bean的时候也可以使用primary属性吗?其实它本质就是会在生成BeanDenifinition的时候设置它的primary属性为true。以下示例代码在BeanDefinitionParserDelegate类中可以找到。
| 1 | //设置primary 属性 | 
那么我们可以猜想下Primary注解是否也是做了同样的事情呢?答案是肯定的。
Spring在扫描它关注的类后,会检查是否标注有Primary注解,如果有的话就设置该BeanDefinitiond的primary属性为true.
扫描关注类的示例源代码如下:
| 1 | protected Set<BeanDefinitionHolder> doScan(String... basePackages) { | 
AnnotationConfigUtils内部会继续对该candidate(候选者)的一些注解做处理:
| 1 | 
 | 
那么标注Bean注解的方法上的Primary注解是否也是这样类似处理的呢?可以说Spring内部尽量让封装进行到底,我们来一探究竟:
| 1 | private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { | 
看到最后一行代码又利用了AnnotationConfigUtils.processCommonDefinitionAnnotations方法来处理我们的Primary注解。
到这里我们又可以猜测Spring最终还是对BeanDefinition的primary属性做筛选判断。
Primary筛选机制
Spring在获取Bean的时候首先会获取到所有的符合所需类型的Bean,我们直奔主题:
| 1 | private <T> NamedBeanHolder<T> resolveNamedBean( | 
上述代码出自DefaultListableBeanFactory,我们可以归纳下这段代码的主要逻辑:首先获取符合Bean类型的所有Bean名称集合,如果集合包含的数量大于1,那么就再遍历该集合继续筛选:如果容器中不包含该beanName所对应的BeanDefinition或者该对应的BeanDefinition的AutowireCandidate属性为true(可以理解为符合自动注入候选者之一),那么就加入autowireCandidates集合。最后如果autowireCandidates集合不为空,那么就把candidateNames重新引用autowireCandidates集合。这里我们也就可以解释上篇文章提到的把相同类型的Bean其中一个的autowire-candidate属性设为false也可以解决问题的原因了。
Spring不是筛选到这里就结束了,它会继续在最新的candidateNames集合上做筛选。请注意下述代码紧跟上述代码之后:
| 1 | private <T> NamedBeanHolder<T> resolveNamedBean( | 
我们主要关注的还是candidateNames集合数量大于1的情况,一上来就会遍历candidateNames集合把对应的beanName以及根据beanName获取到的bean实例获取该bean类型放入candidates这个LinkedHashMap键值对集合里。然后就到了跟本文最为密切相关的函数里了,也就是1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
```java
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();
      //也会去父容器里判断是否是Primary的
      if (isPrimary(candidateBeanName, beanInstance)) {
         if (primaryBeanName != null) {
            //判断当前bean工厂是否有对应的beandefinition
            boolean candidateLocal = containsBeanDefinition(candidateBeanName);
            boolean primaryLocal = containsBeanDefinition(primaryBeanName);
            //判断是否有多于1个的primary bean
            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;
}
这段函数代码逻辑不算复杂,主要就是遍历candidates键值对集合对象(在这里我们也可以学到Map集合遍历的方法:**可以根据它的1
2
3
4
5
6
7
8
9
10
```java
protected boolean isPrimary(String beanName, Object beanInstance) {
   if (containsBeanDefinition(beanName)) {
      return getMergedLocalBeanDefinition(beanName).isPrimary();
   }
   BeanFactory parent = getParentBeanFactory();
   return (parent instanceof DefaultListableBeanFactory &&
         ((DefaultListableBeanFactory) parent).isPrimary(beanName, beanInstance));
}
这里一开始又判断了下是否包含bean名称对应的beanDefintition,如果当前beanFactory有该beanDefintition的话就获取合并过后的BeanDefinition然后返回它的isPrimary属性,没有就去parentBeanFactory中尝试获取。
也就是说isPrimary函数中做了两件事:
- 去本地Bean工厂判断是否有这个Bean定义,有就合并(简单理解合并主要是为了可以获取到继承Bean中的属性)并且返回Primary属性。
- 本地Bean工厂不存在该Bean定义那么就会去ParentBeanFactory中去执行相同逻辑了。
借助源码我们可以清晰地明白Primary筛选的主要过程了。如果Primary筛选过后发现没有符合的该怎么办呢?Spring不是到此结束了,它会继续尝试筛选Priority相关的属性,也就是我们上篇文章提到的解决方案。
Primary筛选失败处理
Primary筛选失败就进入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
```java
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {
   String highestPriorityBeanName = null;
   Integer highestPriority = null;
   for (Map.Entry<String, Object> entry : candidates.entrySet()) {
      String candidateBeanName = entry.getKey();
      Object beanInstance = entry.getValue();
      if (beanInstance != null) {
         //获取优先级@Priority注解
         //通过 OrderComparator 来比较
         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;
}
跟determinePrimaryCandidate函数差不多的逻辑,也是筛选candidates对象集合,主要我们关注它内部的1
2
3
4
5
6
7
8
9
```java
protected Integer getPriority(Object beanInstance) {
   Comparator<Object> comparator = getDependencyComparator();
   if (comparator instanceof OrderComparator) {
      return ((OrderComparator) comparator).getPriority(beanInstance);
   }
   return null;
}
到这里我们可以终于看到上篇文章提到的dependencyComparator的身影了,至于getPriority内部如何去判断已经无关大碍,限于本篇篇幅,有兴趣的朋友可以自行去Spring源码中一探究竟。
总结
从一个Primary注解的角度看Spring的设计:Spring内部的逻辑处理步骤比较深,虽然如此还是保持地很严密,几乎看不出任何漏洞且留给开发人员扩展的点非常多
