从Primary注解的角度看Spring的设计之美

前言

上篇文章我们总结了开发Spring应用时出现NoUniqueBeanDefinitionException问题的解决方案,那么这篇文章我们就来回答下存在多个相同类型的Bean时Primary注解标注的Bean怎么就能够成功注入,也可以借此从点到面领略下Spring的设计之美。

揭秘

Primary注解的本质

还记得我们在使用xml配置Bean的时候也可以使用primary属性吗?其实它本质就是会在生成BeanDenifinition的时候设置它的primary属性为true。以下示例代码在BeanDefinitionParserDelegate类中可以找到。

1
2
3
4
//设置primary 属性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}

那么我们可以猜想下Primary注解是否也是做了同样的事情呢?答案是肯定的。

Spring在扫描它关注的类后,会检查是否标注有Primary注解,如果有的话就设置该BeanDefinitiond的primary属性为true.

扫描关注类的示例源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
//省略部分代码
if (candidate instanceof AnnotatedBeanDefinition) {
//内部会处理@Primary注解
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
//内部调用 GenericApplicationContext registerBeanDefinition 2020-09-11
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

AnnotationConfigUtils内部会继续对该candidate(候选者)的一些注解做处理:

1
2
3
4
5
6
7

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
//省略部分源码
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
}

那么标注Bean注解的方法上的Primary注解是否也是这样类似处理的呢?可以说Spring内部尽量让封装进行到底,我们来一探究竟:

1
2
3
4
5
6
7
8
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
//省略前后代码
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
//省略前后代码
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
}

看到最后一行代码又利用了AnnotationConfigUtils.processCommonDefinitionAnnotations方法来处理我们的Primary注解。

到这里我们又可以猜测Spring最终还是对BeanDefinition的primary属性做筛选判断。

Primary筛选机制

Spring在获取Bean的时候首先会获取到所有的符合所需类型的Bean,我们直奔主题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private <T> NamedBeanHolder<T> resolveNamedBean(
ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException
{


//调用ListableBeanFactory接口的getBeanNamesForType方法
//去 beanDefinitionNames 中去找
String[] candidateNames = getBeanNamesForType(requiredType);

if (candidateNames.length > 1) {
List<String> autowireCandidates = new ArrayList<>(candidateNames.length);
for (String beanName : candidateNames) {
//去 beanDefinitionMap 中去查找 没有 或者 找到了
//只要有一个是 autowireCandidate的 那就只会去找 autowireCandidates的
if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
autowireCandidates.add(beanName);
}
}
//如果autowireCandidates不为空 那就重新赋值candidateNames
//也就是只关心autowireCandidate的 bean
if (!autowireCandidates.isEmpty()) {
candidateNames = StringUtils.toStringArray(autowireCandidates);
}
}
}

上述代码出自DefaultListableBeanFactory,我们可以归纳下这段代码的主要逻辑:首先获取符合Bean类型的所有Bean名称集合,如果集合包含的数量大于1,那么就再遍历该集合继续筛选:如果容器中不包含该beanName所对应的BeanDefinition或者该对应的BeanDefinition的AutowireCandidate属性为true(可以理解为符合自动注入候选者之一),那么就加入autowireCandidates集合。最后如果autowireCandidates集合不为空,那么就把candidateNames重新引用autowireCandidates集合。这里我们也就可以解释上篇文章提到的把相同类型的Bean其中一个的autowire-candidate属性设为false也可以解决问题的原因了。

Spring不是筛选到这里就结束了,它会继续在最新的candidateNames集合上做筛选。请注意下述代码紧跟上述代码之后:

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
35
36
37
38
39
40
41
42
43
private <T> NamedBeanHolder<T> resolveNamedBean(
ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException
{


//紧跟上述代码逻辑
//获取到的候选名称只有1个的情况
if (candidateNames.length == 1) {
String beanName = candidateNames[0];
//内部调用getBean方法
//还是调用 父 抽象类 AbstractBeanFactory的getBean方法 根据名称去get
return new NamedBeanHolder<>(beanName, (T) getBean(beanName, clazz, args));
}
else if (candidateNames.length > 1) {////获取到的候选名称有多个的情况
Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
for (String beanName : candidateNames) {
//如果 已经存在单例对象了 singletonObjects 中 且参数为空
if (containsSingleton(beanName) && args == null) {
//直接获取bean
Object beanInstance = getBean(beanName);
candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance));
}
else {
candidates.put(beanName, getType(beanName));
}
}
//如果候选的bean数量大于1
//检查是否是有Primay
String candidateName = determinePrimaryCandidate(candidates, clazz);
if (candidateName == null) {
//https://blog.csdn.net/gamezjl/article/details/108572474
candidateName = determineHighestPriorityCandidate(candidates, clazz);
}
if (candidateName != null) {
Object beanInstance = candidates.get(candidateName);
if (beanInstance == null || beanInstance instanceof Class) {
beanInstance = getBean(candidateName, clazz, args);
}
return new NamedBeanHolder<>(candidateName, (T) beanInstance);
}
if (!nonUniqueAsNull) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
}
}
}

我们主要关注的还是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函数中做了两件事:

  1. 去本地Bean工厂判断是否有这个Bean定义,有就合并(简单理解合并主要是为了可以获取到继承Bean中的属性)并且返回Primary属性。
  2. 本地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内部的逻辑处理步骤比较深,虽然如此还是保持地很严密,几乎看不出任何漏洞且留给开发人员扩展的点非常多

欢迎关注我的公众号:沉迷Spring
显示 Gitment 评论
0%