Spring Bean 如何设置为默认 Bean?

 作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等

回答

如果我们想将某个 Bean 设置为默认 Bean,一般推荐使用 @Primary。当应用中存在多个候选 Bean 的时候,Spring 会优先选择标记了 @Primary 的那个作为注入的默认 Bean。

详解

举个例子:

@Component
public class SkServiceA implements SkService {
    // Implementation of MyService
}

@Component
@Primary
public class SkServiceB implements SkService {
    // Implementation of MyService
}

在这里,如果某个地方需要注入 SkService ,Spring 会优先选择 @Primary 标注的 SkServiceB。但是,如果我们使用了 @Qualifier 指定了具体的 Bean 名称,那么即使有 @Primary,Spring 也会注入指定的 Bean。

多候选 Bean 问题

Spring 容器在初始化时,如果某个类型有多个 Bean 的候选对象,如果我们不做任何处理,则系统会抛出如下异常:

NoUniqueBeanDefinitionException: No qualifying bean of type 'xxx' available: expected single matching bean but found x: [beanA, beanB]

为了避免这种情况的发生,Spring 提供了两种方式处理:

  1. @Primary:标记一个默认 Bean,当多个候选存在时优先选择。
  2. @Qualifier:明确指定使用某个特定的 Bean。

在目标 Bean 明确的情况下,我们使用 @Qualifier 比较好。

在项目开发过程中,如果我们有一个实现类是主实现类,那么码哥推荐将该实现类添加 @Primary。同时,对于特殊的实现,使用 @Qualifier 来显示指定。在一些场景中,尤其是需要替换第三方库的时候,我们有时候需要覆盖默认实现,则我们就可以在自定义实现上添加 Primary 满足要求。

在实际使用中,我们需要根据业务需求搭配使用 @Primary 和 @Qualifier,以确保依赖注入的明确性和灵活性。

@Primary 的实现原理

我们直接看源码,DefaultListableBeanFactory#doResolveDependency() :

  public 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;
      }

      String autowiredBeanName;
      Object instanceCandidate;

      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,如果 matchingBeans.size() > 1 ,则调用 determineAutowireCandidate()

  protected 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;
  }

在这个方法里面,我们可以看到是一次调用三个方法 determinePrimaryCandidate()determineHighestPriorityCandidate()matchesBeanName() 来确认,不为空就返回:

  • determinePrimaryCandidate()
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();
      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();
    }
    BeanFactory parent = getParentBeanFactory();
    return (parent instanceof DefaultListableBeanFactory &&
        ((DefaultListableBeanFactory) parent).isPrimary(transformedBeanName, beanInstance));
  }

这个方法的逻辑比较简单,迭代调用 isPrimary() 来判断这个 BeanDefinition 上面是否含有 @Primary

  • determineHighestPriorityCandidate():该方法的本质是通过查找 JSR-330 中的 @Priority ,来确定 Bean 的优先级。
  • matchesBeanName():该方法则是通过 BeanName 来进行匹配的。

这两个方法不是我们本篇面试题的重点,就不多介绍了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值