本文的目录结构:处理流程总结(简单 + 详细) -> 源码调试与解析 -> 参考资料
先给出 @Resource 注入的实验结果总结:(环境:springboot 2.2.x + spring 5.x,不考虑和 @Qualifier 配合的情况)
处理流程总结
若未指定 name属性,默认使用字段名或方法名作为 Bean 名称。名称匹配失败后,尝试按类型匹配,结果必须唯一。

详细流程如下:
1、处理器执行入口
@Resource 注入,是在 populationBean 的时候,遍历处理 InstantiationAwareBeanPostProcessor(实例化感知Bean后置处理器)去执行 postProcessProperties,其中就有 CommonAnnotationBeanPostProcessor 在执行 postProcessProperties 方法就是处理 @Resource 注入的。顺便说下,在遍历时 AutowiredAnnotationBeanPostProcessor 也会进行处理,不过是处理 @Autowired 注入的。
2、扫描需要处理的@Resource类变量包装成资源元数据
CommonAnnotationBeanPostProcessor#postProcessProperties 的逻辑是先 findResourceMetadata 去寻找 InjectionMetadata “注入元数据”,底层是通过Class类型去反射获取所有的 Field,判断是否有 @Resource 注解,是的话包装成 ResourceElement 资源元素加入 injectedElements 数组,表示“被注入元素”集合。
3、资源元数据执行注入
后续 ResourceElement#inject 底层会遍历 injectedElements 数组,对单独的“被注入元素”进行inject,尝试将元素注入到目标类。 内部 getResourceToInject 拿到资源,使用 field#set 反射目标字段。
4、按名称查找
getResourceToInject 底层调用 CommonAnnotationBeanPostProcessor#autowireResource 逻辑,表示自动注入资源,基本逻辑是通过名称匹配失败后,尝试按类型匹配。首先 factory.containsBean 判断 Bean 名称是否存在,如果存在则调用 beanFactory.resolveBeanByName 按名称精确查找,否则调用 beanFactory.resolveDependency 方法按类型匹配,如果匹配不到抛出异常。
beanFactory.resolveBeanByName 按名称查找的逻辑,底层调用了 getBean 方法,触发bean的按名称获取,先缓存中获取,不存在时创建,执行 bean 的实例化、属性注入、初始化,最后返回进行赋值。
5、按类型查找
beanFactory.resolveDependency 按类型匹配的逻辑,执行逻辑实现是 DefaultListableBeanFactory#doResolveDependency,通过 findAutowireCandidates 查找自动注入的匹配候选者 matchingBeans,候选者0个就返回null,候选者1个就是成功返回1个,大于1个会抛出异常。
其中,findAutowireCandidates 调用了 BeanFactoryUtils#beanNamesForTypeIncludingAncestors,底层通过遍历 beanDefinitionNames,对每一个 Bean 进行 Class#isAssignableFrom 判断,如果具有可赋值性,则是匹配的。

源码调试与解析
接下来是源码解析部分,跟着我进行调试和分析,你会有更深的感悟!
用例如下:
@Component
class InjectionTest {
@Resource
private InjectionService injectionService;
}
@Component("injectionServiceA")
class InjectionServiceA implements InjectionService {
}
@Component("injectionServiceB")
class InjectionServiceB implements InjectionService {
}
调用链路是这样的,从我们熟知的 populateBean 开始:
创建流程不熟悉的话,可以参考我们的历史文章 《Spring源码 - Spring IOC 如何解决循环依赖》和 《Spring源码 - 这才是Spring Bean生命周期》

在 populateBean 注入 Bean 的时候,遍历所有的 BeanPostProcessor ,这时候过滤出 所有的 InstantiationAwareBeanPostProcessor 类型进行处理。
InstantiationAwareBeanPostProcessor 的功能是什么?源码里有写到:BeanPostProcessor 的子接口,它添加了一个实例化前回调,以及一个实例化之后但在设置显式属性或发生自动装配之前发生的回调。通常用于抑制特定目标 bean 的默认实例化,例如创建具有特殊TargetSources的代理(池化目标、延迟初始化目标等),或实现其他注入策略,例如字段注入。注意:该接口为特殊用途接口,主要供框架内部使用。

会有这么几个实现,其中, CommonAnnotationBeanPostProcessor 是处理 @Resource 的,AutowiredAnnotationBeanPostProcessor 是处理 @Autowired 的,这几个在遍历阶段其实都会执行处理逻辑。
这几个后置处理器在 Spring 容器早期就维护到了容器中,具体是如何维护进去的可以试着关注,后续有可能会单独讲呢~(画个饼)
我们进入到了 CommonAnnotationBeanPostProcessor#postProcessProperties 这个方法,去处理依赖的选择和注入。
postProcessProperties 接口的注释描述:在工厂将给定的属性值应用于给定的 bean 之前,对它们进行后处理,而无需任何属性描述符。如果实现提供自定义 postProcessPropertyValues 实现,则应返回 null(默认值),否则返回 pvs。在此接口的未来版本中(删除了 postProcessPropertyValues),默认实现将直接按原样返回给定的 pvs。

具体是通过 findResourceMetadata 方法去获取 InjectionMetadata,我们先不要看是如何获取到的,我们先关注这个获取到的东西是什么。这个对象内部有 injectedElements,也就是被注入的元素集合,后续是都要被遍历去注入的,也有 targetClass,注入的目标类。有了元数据,我们就知道要把哪些字段注入到哪个了,这个元数据就是注入的基础。
InjectionMetadata 作用,源码里写的:用于管理注入元数据的内部类。不用于应用。
这里不知道你有没有发现一个“灵异点”,就是findResourceMetadata方法传入了 bean,怎么就能找到injectedElements,也就是要注入的对象的信息的?我们深入去探究这些待注入属性是如何找到的:

buildResourceMetadata 的关键逻辑如下(截取部分):
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 2. 处理字段上的注解
ReflectionUtils.doWithLocalFields(targetClass, field -> {
if (field.isAnnotationPresent(Resource.class)) { // 2.3 处理 @Resource 注解字段
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new ResourceElement(field, field, null));
}
}
});
buildResourceMetadata 是用反射拿到 Class 的 field 进行遍历,看看是不是有 @Resource 注解,如果是的话判断是非静态字段、也不是被忽略的类型(我调试中只有 javax.xml.ws.WebServiceContext),就将其加入数组(包装成 ResourceElement,很符合 @Resource 注入的语义)。

好的,我现在知道了 Spring 是如何将标记了 @Resource 注解的字段加入到元数据中了。我们回到注入流程,看看 InjectionMetadata#inject 执行了什么逻辑:

遍历了 待注入 的元素,挨个注入:

应该很简单就可以看懂,就是 通过 Field#set,设置目标对象 target 里 @Resource 标记的成员属性的值为 getResourceToInject 的结果。
我们根据链路 ResourceElement#getResourceToInject -> CommonAnnotationBeanPostProcessor#getResource -> CommonAnnotationBeanPostProcessor#autowireResource ,跳过前两个直接往下来到 autowireResource 方法:

这里逻辑为:优先通过 factory.containsBean 判断是否存在,如果是则按名称精确查找,否则按类型匹配,假如类型匹配结果为 null,那么抛出异常。
先看按名称匹配的,beanFactory.resolveBeanByName 底层调用了 getBean 方法,就是 bean 获取,必要时创建的核心方法,老朋友了,获取到的 bean 返回出去当然就作为 field.set 的内容赋值给 target 了。

不过呢,由于是名称匹配,所以有可能类型不匹配,这时候就会抛出 BeanNotOfRequiredTypeException 异常,对应的就是 @Resource private InjectionServiceA injectionServiceB 这种注入方式了。

现在再看按类型匹配的。
这里可能有疑问了,按类型注入可行吗,这里没看到类型的?其实,是用了装饰器模式,用 element 去获取依赖装饰器,这个装饰器的构建是解析了 member 这个 Field 对象的内容,反射得到 class 类型等几个关键信息。这个装饰器的命名也很应景,叫 LookupDependencyDescription,专门来“查找依赖”用的装饰器。


那我们拿到了 字段名称、字段 Field、字段类型的 Class的装饰器,我们的 beanName 在 factory 里不 containsBean,那么就可以去根据类型获取到具体的对象了,然后尽快返回给 Field#set 的 第二个参数,完成属性赋值(也就是注入)。
不过呢,不是说有类型 Class 就一定可以拿到,或者拿到的不一定唯一,所以后续还有对此的获取逻辑,我们继续深挖,如果是此用例,看看不唯一是怎么识别的:
我们的 AutowireCapableBeanFactory 是 DefaultListableBeanFactory,执行其 resolveDependency,会进入到 DefaultListableBeanFactory#doResolveDependency(someJob -> doSomeJob 的方法关系很容易知道其关系)。

这里有查询候选 Bean,如果 matchingBeans 匹配的 Bean 数组数量有多个(大于1),则会抛出异常。如果没有匹配,返回null后,外层其实也是会抛出异常的,毕竟都通过名称、类型都失败了,注入肯定失败。
类型判断的依据是什么呢?是父类,还是可赋值性?继续深挖。
DefaultListableBeanFactory#findAutowireCandidates -> BeanFactoryUtils#beanNamesForTypeIncludingAncestors -> DefaultListableBeanFactory#getBeanNamesForType -> DefaultListableBeanFactory#doGetBeanNamesForType -> (循环 beanDefinitionNames 对每一个 bean 进行识别)-> AbstractBeanFactory#isTypeMatch -> ResolvableType#isInstance -> ResolvableType#isAssignableFrom -> ClassUtils#isAssignable -> Class#isAssignableFrom
走到了 java.lang.Class#isAssignableFrom 这个 native 方法。
java.lang.Class#isAssignableFrom 的注释:确定此 Class 对象表示的类或接口是否与指定的 Class 参数表示的类或接口相同,或者是该类或接口的超类或超接口。如果是这样,则返回 true;否则返回 false。如果此 Class 对象表示基元类型,则如果指定的 Class 参数正是此 Class 对象,则此方法返回 true;否则返回 false。具体来说,此方法测试指定的 Class 参数表示的类型是否可以通过标识转换或扩大引用转换转换为此 Class 对象表示的类型。有关详细信息,请参阅 Java 语言规范的 5.1.1 和 5.1.4 节。
也就是说,类型匹配最终是判断“可赋值性”, 也就是需要满足多态的赋值。
建议你回到顶部,复习下 “简单版 + 详细版” 的总结,相信你一定能看懂了~
参考资料
参考:
Spring 源码:https://github.com/spring-projects/spring-framework
关注公众号:【源码启示录】,解锁更多源码知识

7930

被折叠的 条评论
为什么被折叠?



