Autowired的注入过程源码解析

 更新时间:2022年09月03日 14:34:26   作者:杨同学_  
这篇文章主要为大家介绍了Autowired的注入过程源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

一、案例场景

在使用 @Autowired 时,你或多或少都会遇过类似的错误:

required a single bean, but 2 were found

为了重现这个错误,我们可以先写一个案例来模拟下。

@RestController
@Slf4j
@Validated
public class StudentController {
    @Autowired
    DataService dataService;
    @RequestMapping(path = "students/{id}", method = RequestMethod.DELETE)
    public void deleteStudent(@PathVariable("id") @Range(min = 1,max = 100) int id) {
        dataService.deleteStudent(id);
    }
}

其中 DataService 是一个接口,其实现依托于 Oracle,代码示意如下:

public interface DataService {
    void deleteStudent(int id);
} 
@Repository
@Slf4j
public class OracleDataService implements DataService {
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by oracle");
    }
}

截止目前,运行并测试程序是毫无问题的。直到某天,我们接到节约成本的需求,希望把一些部分非核心的业务从 Oracle 迁移到社区版 Cassandra,所以我们自然会先添加上一个新的 DataService 实现,代码如下:

@Repository
@Slf4j
public class CassandraDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by cassandra");
    }
}

此时,程序就已经无法启动了,报错如下:

image-20220719210803377

二、案例解析

首先,我们先来了解下 @Autowired 发生的位置和核心过程。当一个 Bean 被构建时,核心包括两个基本步骤:

  • 执行 AbstractAutowireCapableBeanFactory#createBeanInstance 方法:通过构造器反射构造出这个 Bean,在此案例中相当于构建出 StudentController 的实例;
  • 执行 AbstractAutowireCapableBeanFactory#populateBean 方法:填充(即设置)这个 Bean,在本案例中,相当于设置 StudentController 实例中被 @Autowired 标记的 dataService 属性成员。

在步骤 2 中,“填充”过程的关键就是执行各种 BeanPostProcessor 处理器,关键代码如下:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  //省略非关键代码
  for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof InstantiationAwareBeanPostProcessor) {
      InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
      PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
      //省略非关键代码
    }
  }
}

在上述代码执行过程中,因为 StudentController 含有标记为 Autowired 的成员属性 dataService,所以会使用到AutowiredAnnotationBeanPostProcessor(BeanPostProcessor 中的一种)来完成“装配”过程:找出合适的 DataService 的 bean 并设置给StudentController#dataService。如果深究这个装配过程,又可以细分为两个步骤:

  • 寻找出所有需要依赖注入的字段和方法,参考 AutowiredAnnotationBeanPostProcessor#postProcessProperties 中的代码行:
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
  • 根据依赖信息寻找出依赖并完成注入,以字段注入为例,参考 AutowiredFieldElement#inject 方法:
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    //省略非关键代码
    DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
    //寻找“依赖”,desc为"dataService"的DependencyDescriptor
    value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    //省略非关键代码
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        //装配“依赖”
        field.set(bean, value);
    }
}

当我们根据 DataService 这个类型来找出依赖时,我们会找出 2 个依赖,分别为 CassandraDataService 和 OracleDataService。在这样的情况下,如果同时满足以下两个条件则会抛出本案例的错误:

  • 调用 determineAutowireCandidate 方法来选出优先级最高的依赖,但是发现并没有优先级可依据。具体选择过程可参考DefaultListableBeanFactory#determineAutowireCandidate。

优先级的决策是先根据 @Primary 来决策,其次是 @Priority 决策,最后是根据 Bean 名字的严格匹配来决策。如果这些帮助决策优先级的注解都没有被使用,名字也不精确匹配,则返回 null,告知无法决策出哪种最合适。

  • @Autowired 要求是必须注入的(即 required 保持默认值为 true),或者注解的属性类型并不是可以接受多个 Bean 的类型,例如数组、Map、集合。这点可以参考 DefaultListableBeanFactory#indicatesMultipleBeans 的实现。

三、问题修正

第一,我们可以通过使用标记 @Primary 的方式来让被标记的候选者有更高优先级,从而避免报错。

@Repository
@Primary
@Slf4j
public class OracleDataService implements DataService{
  //省略非关键代码
}

但是这种方式并不一定符合业务需求。

第二,我们可以使用下面的方式去修改:

@Autowired
DataService oracleDataService;

将属性名和 Bean 名字精确匹配,这样就可以让注入选择不犯难:需要 Oracle 时指定属性名为 oracleDataService,需要 Cassandra 时则指定属性名为 cassandraDataService。

第三,还可以采用 @Qualifier 来显式指定引用的是那种服务,例如采用下面的方式:

@Autowired
@Qualifier("cassandraDataService")
DataService dataService;

这种方式之所以能解决问题,在于它能让寻找出的 Bean 只有一个(即精确匹配),所以压根不会出现后面的决策过程,可以参考 DefaultListableBeanFactory#doResolveDependency。

public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
		Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
    //...
    Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
    //...
}

以上就是Autowired的注入过程源码解析的详细内容,更多关于Autowired注入过程的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot+Idea热部署实现流程解析

    SpringBoot+Idea热部署实现流程解析

    这篇文章主要介绍了SpringBoot+Idea热部署实现流程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • java实现String字符串处理各种类型转换

    java实现String字符串处理各种类型转换

    在日常的程序开发中,经常会涉及到不同类型之间的转换,本文主要介绍了String字符串处理各种类型转换,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • Java之JSF框架案例详解

    Java之JSF框架案例详解

    这篇文章主要介绍了Java之JSF框架案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • 浅谈Maven安装及环境配置出错的解决办法

    浅谈Maven安装及环境配置出错的解决办法

    这篇文章主要介绍了浅谈Maven安装及环境配置出错的解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 详解spring boot使用@Retryable来进行重处理

    详解spring boot使用@Retryable来进行重处理

    本篇文章主要介绍了详解spring boot使用@Retryable来进行重处理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java Spring5学习之JdbcTemplate详解

    Java Spring5学习之JdbcTemplate详解

    这篇文章主要介绍了Java Spring5学习之JdbcTemplate详解,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-05-05
  • 详解spring boot 以jar的方式启动常用shell脚本

    详解spring boot 以jar的方式启动常用shell脚本

    本篇文章主要介绍了详解spring boot 以jar的方式启动常用shell脚本,具有一定的参考价值,有兴趣的可以了解一下
    2017-09-09
  • Spring之什么是ObjectFactory?什么是ObjectProvider?

    Spring之什么是ObjectFactory?什么是ObjectProvider?

    这篇文章主要介绍了Spring之什么是ObjectFactory?什么是ObjectProvider?具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • 浅谈JAVA 异常对于性能的影响

    浅谈JAVA 异常对于性能的影响

    Java的异常处理为什么会影响性能?异常开销很大。那么,这是不是就意味着您不该使用异常?当然不是。但是,何时应该使用异常,何时又不应该使用异常呢?不幸的是,答案不是一下子就说得清楚的,我们来详细探讨下。
    2015-05-05
  • java 如何读取远程主机文件

    java 如何读取远程主机文件

    这篇文章主要介绍了java 如何读取远程主机文件的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02

最新评论