1. 问题剖析

当咱们应用 Spring 的时候,有时候会遇到上面这种状况。

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

@Componentpublic class A {    @Autowired    B b;}

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

@Configuration@ComponentScanpublic class JavaConfig {    @Bean("b1")    B b1() {        return new B();    }    @Bean("b2")    B b2() {        return new B();    }}

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

当然,对于这样的问题,置信有教训的同学都晓得该怎么解决:

  1. 能够应用 @Resource 注解,应用该注解时指定具体的 Bean 名称即可。
  2. 在 @Autowired 注解之上,再多加一个 @Qualifier("b1") 注解,通过该注解去指定要加载的 Bean 名称。
@Componentpublic class A {    @Autowired    @Qualifier("b1")    B b;}
  1. 在多个 B 对象的某一个之上,增加 @Primary 注解,示意当存在反复的 B 对象时,优先应用哪一个。
@Configuration@ComponentScanpublic class JavaConfig {    @Bean("b1")    @Primary    B b1() {        return new B();    }    @Bean("b2")    B 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<String, Object> 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 didn't 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<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;}

这个办法里边一共做了三种尝试:

  1. 第一个尝试就是调用 determinePrimaryCandidate 办法去确定最佳候选 Bean,这个办法实质上就是通过 @Primary 注解找到最佳 BeanName。
  2. 如果第一步没有找到最佳 BeanName,那么接下来会调用 determineHighestPriorityCandidate 办法去查找最佳 Bean,该办法实质上是通过查找 JSR-330 中的 @Priority 注解,来确定 Bean 的优先级。
  3. 如果前两步都没找到适合的 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<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();    }    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<String, Object> 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<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;    }}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    B b2() {        return new B();    }}

3. 小结

好啦,通过下面的剖析,当初小伙伴们明确了 @Primary 注解的残缺解决逻辑了吧~本文联合@Autowired 到底是怎么把变量注入进来的? 和 Spring 中 @Qualifier 注解还能这么用? 一起食用成果更好哦!