前言
上篇文章我们总结了开发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内部的逻辑处理步骤比较深,虽然如此还是保持地很严密,几乎看不出任何漏洞且留给开发人员扩展的点非常多