我们使用Spring开发过程中经常会用到Autowired注解注入依赖的bean,这部分也是面试的热点问题之一。今天咱们一起来深入研究下自动注入的背后实现原理。首先上一个例子,如下所示:

@RestController
public class TestController {
@Autowired
List<ICheckRuleService> checkRuleService; @RequestMapping("/test")
public void test(){
checkRuleService.forEach(x->x.valid());
}
}

populateBean

Autowired是怎么实现自动注入的呢,今天我们来通过源码分析一下。当Spring创建 TestController Bean时,会调用AbstractBeanFactory#doGetBean(如果对Spring创建Bean流程不熟的读者,可以给我留言,后面考虑是否写个IOC系列),doGetBean里面会调用doCreateBean()方法去创建Bean,创建Bean之后,会对Bean进行填充

try {
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
}

populateBean 里有这样一段代码,看起来是处理Autowired的,分别是autowireByName 和 autowireByType

PropertyValues pvs = mbd.hasPropertyValues() ? mbd.getPropertyValues() : null;
if (mbd.getResolvedAutowireMode() == 1 || mbd.getResolvedAutowireMode() == 2) {
MutablePropertyValues newPvs = new MutablePropertyValues((PropertyValues)pvs);
if (mbd.getResolvedAutowireMode() == 1) {
this.autowireByName(beanName, mbd, bw, newPvs);
} if (mbd.getResolvedAutowireMode() == 2) {
this.autowireByType(beanName, mbd, bw, newPvs);
} pvs = newPvs;
}

我们来验证一下,通过断点调试我们发现并不会进入if里,所以自动注入并不是这里实现的。那这里有什么用呢,先放一放,后面再说。

AutowiredAnnotationBeanPostProcessor

那么到底是哪里注入进去的呢?我们继续往下看,在这段代码下方有个BeanPostProcessor的逻辑,通过断点我们发现有个AutowiredAnnotationBeanPostProcessor 的后置处理器,当这个BeanPostProcessor执行完 postProcessPropertyValues方法后,testController的checkRuleService 属性就有了值了,说明属性值注入肯定和 AutowiredAnnotationBeanPostProcessor 有关,我们跟进去看一下

进入AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues 方法里,里面主要有两部分逻辑

  • 首先看到一段 findAutowiringMetadata 的逻辑,根据方法名称知道是获取当前bean的注入元信息

  • 调用 metadata.inject 注入属性

public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs); try {
metadata.inject(bean, beanName, pvs);
return pvs;
} catch (BeanCreationException var7) {
throw var7;
} catch (Throwable var8) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var8);
}
}

我们先来看第一部分:findAutowiringMetadata

我们进入findAutowiringMetadata,看下它的逻辑,先从 injectionMetadataCache 缓存里取,如果取不到值,则调用buildAutowiringMetadata 构建 InjectionMetadata ,构建成功后设置到缓存里。

    private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
InjectionMetadata metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized(this.injectionMetadataCache) {
metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
} metadata = this.buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
} return metadata;
}

我们来看下 buildAutowiringMetadata,继续跟进去,源码如下:

里面是通过当前Bean的Class反射获取 Field 和 Method ,然后对 Field 和 Method 分别调 findAutowiredAnnotation 方法获取自动注入的注解,然后根据注解类型是否required构建不同类型的InjectedElement。

  • AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement:

boolean required = this.determineRequiredStatus(ann);
currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement(field, required));
  • AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement:

boolean required = this.determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement(method, required, pd));

补充:通过AutowiredAnnotationBeanPostProcessor 构造函数我们知道,自动注入处理的是被 @Autowired 和 @Value 这两个注解标注的属性(Field)或方法(Method):

    public AutowiredAnnotationBeanPostProcessor() {

        this.autowiredAnnotationTypes.add(Autowired.class);

        this.autowiredAnnotationTypes.add(Value.class);

    //......

到这里,需要注入的元数据信息就已经构建完成了,接下来就要到注入部分了。来看下 postProcessPropertyValues 的第二部分。

再看第二部分:metadata.inject

前面获取到了需要注入的元数据信息,接下来是元数据 inject 的实现,继续跟进去,里面是一个for循环,循环调用了element的inject方法

if (!((Collection)elementsToIterate).isEmpty()) {
for(Iterator var6 = ((Collection)elementsToIterate).iterator(); var6.hasNext(); element.inject(target, beanName, pvs)) {
element = (InjectionMetadata.InjectedElement)var6.next();
if (logger.isDebugEnabled()) {
logger.debug("Processing injected element of bean '" + beanName + "': " + element);
}
}
}

我们断点调试进去,发现element的真实类型是AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement,而当前element 真实类型是 TestController.checkRuleService 的集合。

我们进入AutowiredFieldElement#inject方法,首先尝试从缓存里拿当前Field的值,肯定拿不到,所以走的是else分支,else分支里从beanFactory里解析当前Field属性值

value = AutowiredAnnotationBeanPostProcessor.this.beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

继续跟进去,发现其实调用的 doResolveDependency 方法

越来越接近真相了,不要着急,继续跟进去

发现一个类型为Object的 multipleBeans ,结果返回的也是这个Object,我们大胆猜测这个Object就是我们需要注入的List属性,继续跟进去验证一下:

我们看一下 Collection 分支的源码

 else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
elementType = descriptor.getResolvableType().asCollection().resolveGeneric(new int[0]);
if (elementType == null) {
return null;
} else {
Map<String, Object> matchingBeans = this.findAutowireCandidates(beanName, elementType, new DefaultListableBeanFactory.MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
} else {
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}

TypeConverter converter = typeConverter != null ? typeConverter : this.getTypeConverter();
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
if (this.getDependencyComparator() != null && result instanceof List) {
((List)result).sort(this.adaptDependencyComparator(matchingBeans));
}

return result;
}
}
}

里面是调用了 findAutowireCandidates 来获取Bean,findAutowireCandidates 内部会获取到依赖的BeanNames,然后根据beanName 循环调用beanFactory#getBean 获取需要注入的bean

this.findAutowireCandidates(beanName,elementType,new DefaultListableBeanFactory.MultiElementDescriptor(descriptor))

beanFactory#getBean方法,最终会调用 AbstractBeanFactory#doGetBean,获取到需要装配进去的属性bean。

    public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException {
return beanFactory.getBean(beanName);
}

当所有的循环执行完毕,就获取到了 multipleBeans ,验证了前面的猜测。真是太不容易,赶紧设置缓存

最终通过field.set 将获取到的List属性值value设置到当前bean里,代码如下:

if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}

执行field的set方法后,再来看checkRuleService属性就有值了

如果是Method注入,对应的就是通过反射调用 method.invoke 将属性设置到方法参数里,大致流程差不多。到此,Autowired 装配流程也就结束了。

前面在讲到 populateBean 的时候,有个根据 autowireMode 判断是否执行属性注入,当时获取的autowireMode==0,那么什么时候autowireMode 会有值并且会根据autowireByName 和 autowireByType来装配呢?

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)

其实也很好理解,通过源码我们知道,这里的 mbd 是一个 RootBeanDefinition ,也就是说这里的 mbd.getResolvedAutowireMode()获取的值是通过Bean定义或者通过PostProcessor拿到BeanDefinition,然后设置了AutowireMode属性才会有值。当我们查看这里的autowireByType源码(AbstractAutowireCapableBeanFactory#autowireByType)可以发现,其实autowireByType也是会调用resolveDependency,继续跟进去,发现其实调用的 doResolveDependency 方法,而AutowiredAnnotationBeanPostProcessor 也是通过这个方法实现的自动注入,后面的流程就都一样了。

最后总结一下

1、bean创建完成后,会调用 populateBean() 填充Bean,在populateBean()方法里会获取所有的BeanPostProcessor,并循环执行 BeanPostProcessor#postProcessPropertyValues() 设置属性

2、其中有个AutowiredAnnotationBeanPostProcessor,这个处理器里会根据当前Bean的Class,通过反射获取 Field 和 Method ,分别获取 Field 和 Method 上的自动注入的注解(@Autowired 和 @Value),构建注入元数据InjectionMetadata

3、调用注入元数据InjectionMetadata的 inject() 方法,装配属性(有两种:AutowiredFieldElement 和AutowiredMethodElement),会调用this.beanFactory.resolveDependency(desc,beanName,autowiredBeanNames, typeConverter) 解析依赖的属性值

4、resolveDependency 最终会调用到 resolveMultipleBeans ,而 resolveMultipleBeans 会根据当前注入属性的类型分别按 Array、Collection、Map 走不同的分支,在分支里调用 findAutowireCandidates 获取注入bean的实例,最终回调到 AbstractBeanFactory#doGetBean

5、获取到所有需要注入的属性 bean 实例后,通过反射设置到对应的属性或方法里去,就完成了自动注入全流程了

Spring源码之自动装配的更多相关文章

  1. 【Spring 源码】Spring 加载资源并装配对象的过程(XmlBeanDefinitionReader)

    Spring 加载资源并装配对象过程 在Spring中对XML配置文件的解析从3.1版本开始不再推荐使用XmlBeanFactory而是使用XmlBeanDefinitionReader. Class ...

  2. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  3. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

  4. 【Spring源码分析】原型Bean实例化过程、byName与byType及FactoryBean获取Bean源码实现

    原型Bean加载过程 之前的文章,分析了非懒加载的单例Bean整个加载过程,除了非懒加载的单例Bean之外,Spring中还有一种Bean就是原型(Prototype)的Bean,看一下定义方式: & ...

  5. 剑指Spring源码(二)

    这是春节后的第一篇博客,我在构思这篇博客的时候,一度想放弃,想想要不要换个东西写,因为毕竟个人水平有限,Spring源码实在博大精深,不是我这个菜的抠脚的菜鸡可以驾驭的,怕误人子弟,还有就是源码分析类 ...

  6. Spring 源码分析之 bean 依赖注入原理(注入属性)

         最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...

  7. spring源码分析系列

    spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor spring源码分析系列 ...

  8. 【Spring源码分析】Bean加载流程概览(转)

    转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...

  9. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

随机推荐

  1. 消息队列之Kafka——从架构技术重新理解Kafka

    Apache Kafka® 是 一个分布式流处理平台. 这到底意味着什么呢? 我们知道流处理平台有以下三种特性: 可以让你发布和订阅流式的记录.这一方面与消息队列或者企业消息系统类似. 可以储存流式的 ...

  2. equals(), "== ",hashcode() 详细解释

    Object 通用方法容易混淆的定义 先搞清楚各自的定义 "==" 用来判断 相等 equals() 用来判断 等价 hashcode() 用来返回散列值 "==&quo ...

  3. docker 容器核心技术

    容器的数据卷(volume)也是占用磁盘空间,可以通过以下命令删除失效的volume: [root@localhost]# sudo docker volume rm $(docker volume ...

  4. k8s搭建实操记录干货二(node)

    #注:172.16.110.111为master,172.16.110.112\114为node1\node2(kubeadm join部分要等master完成后手工操作,其它可执行本脚本一键安装) ...

  5. UILabel折行问题 从文件输入文本

    当内存的字符串对象中有\n时,该字符串会在UILabel展示时进行折行. 如果字符串是从plist中拿到的 1,字符串在plist中输入时打入过回车(即字符串在plist中展示也是折行的),那么字符串 ...

  6. UVALive3720

    题目大意:见刘汝佳<算法竞赛入门经典——训练指南>P173. 解题思路: 问题可以转化为求共有多少条过点阵中的点的斜线.其中必定包含左斜线和右斜线,由于点阵式对称的,所以我们只需求出左右斜 ...

  7. Write a merge sort program

    Merge Sort- Recursion Write a merge sort program in JavaScript. Sample array : [34, 7, 23, 32, 5, 62 ...

  8. 一次 nginx 返回 302 问题解决

    1.问题描述: 应用服务器通过post方式向nginx服务器发送http请求,返回 302 2.问题解决过程 2.1.查询nginx日志,开始以为302错误会在nginx的错误日志error.log, ...

  9. MIT6.828准备:MacOS下搭建xv6和risc-v环境

    本文介绍在MacOS下搭建Mit6.828/6.S081 fall2019实验环境的详细过程,包括riscv工具链.qemu和xv6,对于Linux系统同样可以参考. 介绍 只有了解底层原理才能写好上 ...

  10. 4 CSS文本属性

    CSStext(文本)属性可定义文本外观,比如文本颜色,对齐文本,装饰文本,文本缩进,行间距等 4.1文本颜色 color属性用于定义文本颜色. div { color: red; } 颜色表示方法: ...