Spring源码分析之依赖注入(一)

一、前言

主要讲解了在依赖注入过程中, 注入点的获取和属性的注入。

二、前置学习

依赖注入的几种方式

@Bean注解注入(已废弃)

使用以下这种方式去注入, 源码分析会用到

Spring源码分析之依赖注入(一)_第1张图片

自动注入

源码位置

实例化Bean方法: AbstractAutowireCapableBeanFactory.doCreateBean(), 在这个方法里面有一个属性填充的方法:

doCreateBean没印象或者不知道的, 建议从头开始看源代码, 从头走一遍, 才更清晰明了

Spring源码分析之依赖注入(一)_第2张图片

点进去, 这个方法就是依赖注入相关源代码

Spring源码分析之依赖注入(一)_第3张图片

三、@Bean注解注入分析

在我们的源码方法 populateBean里面, 有一段这样的代码

Spring源码分析之依赖注入(一)_第4张图片

如果我们使用 @Bean(autowire = Autowire.BY_TYPE) 这种方式去注入的话, 那么就会进入上图中红框位置的 if判断

AUTOWIRE_BY_NAME

假设我们使用的是 @Bean(autowire = Autowire.BY_NAME)方式, 那么就会进入以下方法

Spring源码分析之依赖注入(一)_第5张图片

unsatisfiedNonSimpleProperties方法

在上面的 autowireByName方法中, 又是通过了 unsatisfiedNonSimpleProperties(mbd, bw);方法去获取 Bean中能进行自动注入的属性名, 我们进入这个方法看一下

Spring源码分析之依赖注入(一)_第6张图片

其中画红框的, 叫做属性描述器, 是由java.beans提供的, 在这里获取当前 Bean的类里面具体的属性和其getter, setter方法

当一个属性是 private作用域且有对应的 getter和 setter方法, 那么 java就认为他是一个真正的属性

接下来的流程判断什么样的属性能进行自动注入?:

  • 该属性有对应的set方法
  • 没有在ignoredDependencyTypes中
  • 如果该属性对应的set方法是实现的某个接口中所定义的,那么接口没有在ignoredDependencyInterfaces中
  • 属性类型不是简单类型,比如int、Integer、int[]

简单类型如下图所示

Spring源码分析之依赖注入(一)_第7张图片

进入循环

执行完 unsatisfiedNonSimpleProperties方法获取到当前 Bean中能进行自动注入的属性名之后进入循环遍历

Spring源码分析之依赖注入(一)_第8张图片

进入这个循环之后的简单流程如下:

  • 通过注入属性的名字去 getBean, 获取 bean对象
  • 将对象加到 MutablePropertyValues集合中
  • 记录一下propertyName对应的Bean被beanName给依赖了

AUTOWIRE_BY_TYPE

AUTOWIRE_BY_TYPE实际上和 AUTOWIRE_BY_NAME是一样的流程, 代码就不详细说明了, 流程如下:

  • 找出 setter对应的属性名
  • 遍历
  • 把对象找出来
  • 找出当前属性描述器对应的类型
    • 找参数的类型
  • 调用resolveDependency方法找到对象
  • 将对象加到 MutablePropertyValues集合中
  • 记录一下propertyName对应的Bean被beanName给依赖了

resolveDependency方法是比较重要的, 后续会专门讲一下

代码如下图所示:

Spring源码分析之依赖注入(一)_第9张图片

四、处理 @Autowired, @Resource等注解

如图所示, 我们是执行了第二个红框内的方法去处理 @Autowired, @Resource等注解的, 通过上面第一个红框也可以看到, 他具体是通过后置处理器来实现的

Spring源码分析之依赖注入(一)_第10张图片

我们进入第一个红框内的 InstantiationAwareBeanPostProcessor接口, 去看他具体的实现

Spring源码分析之依赖注入(一)_第11张图片

在上图中

  • 红框一的那个实现类, 是处理 @Autowired和@Value注解的
  • 红框二的那个实现类, 是处理 @Resource注解的

寻找注入点

我们进入 AutowiredAnnotationBeanPostProcessor类中

在这个类里面有一个 postProcessMergedBeanDefinition方法

通过之前看源码的可知, 我们会先执行postProcessMergedBeanDefinition这个方法, 继续看里面的方法

Spring源码分析之依赖注入(一)_第12张图片

根据当前 Bean的类去找注入点(属性或者某个方法上面加了 @Autowired注解), 具体方法是 buildAutowiringMetadata(clazz);

点进这个方法内部

判断当前类需不需要找注入点

例如: 如果一个Bean的类型是String, 那么则根本不需要进行依赖注入

Spring源码分析之依赖注入(一)_第13张图片

这个方法里面就是各种方法跳转, 就不贴图了, 最后会进入下面这个方法, 大概意思是如果类名字是以 java. 开头的话就不用找注入点了

遍历当前 Bean所有的字段

Spring源码分析之依赖注入(一)_第14张图片

这里有一个lambda表达式, 他会去判断field上是否存在@Autowired、@Value、@Inject中的其中一个, 具体实现如下

Spring源码分析之依赖注入(一)_第15张图片

如上图所示, 他会遍历一个 autowiredAnnotationTypes的 Set集合, 这个列表所属的类有一个构造方法, 在构造方法的内部为这个集合添加了 @Autowired注解和 @Value注解

在上图的 findAutowiredAnnotation()方法中, 它会去遍历当前类上的所有注解, 有没有刚刚配置在 Set集合中的注解

static不进行注入

如图所示, 需要注意的是, 如果当前的属性是 static的, 那么直接return掉, 不进行属性的注入

Spring源码分析之依赖注入(一)_第16张图片

为什么一个字段是static的, 不将其当做注入点

如果我们的 Bean对象不是单例的, 而是多例或者其他类型的, 那么获取一次Bean都会将其重新创建一遍, 这不符合我们对 static静态资源的预期

如果获取到相关的注解

如果获取到相关的注解了, 那么继续往下走,会先执行一个方法, 然后把当前这个字段设置为一个注入点

Spring源码分析之依赖注入(一)_第17张图片

在我们的 @Autowired注解中, 可以进行配置 required, 这个值默认是true的, 他的含义是: 如果在依赖注入的时候, 没有找到这个 Bean是会报错的, 如果设置为 false之后, 在依赖注入期间没有找到这个 Bean就不会报错了

Spring源码分析之依赖注入(一)_第18张图片

下面的代码, 就是对 @Autowired注解属性配置的单独处理

boolean required = determineRequiredStatus(ann);
复制代码

遍历当前 Bean所有的方法

接着往下走, 就是遍历当前 Bean所有的方法了

Spring源码分析之依赖注入(一)_第19张图片

还是一样的, 简单流程如下:

  • 过滤掉桥接方法
  • 去找方法上面有没有加注解
  • 是不是静态方法
  • 某个方法的参数等于0, 则打印日志
  • 解析 required值
  • 封装成AutowiredMethodElement类
  • 当成注入点加入 currElements集合中

桥接方法, 是专门用来处理一些特殊的情况的

如下图所示, 如果是用以下这种方式编写代码, 那么在字节码文件中就会发现两个 setOrderService方法, 这个时候就需要桥接方法帮助我们找到原方法

Spring源码分析之依赖注入(一)_第20张图片

遍历父类, 去循环上面的操作

注意, 我们这个方法是一个 do while循环, 先执行当前 Bean再去遍历 父Bean

Spring源码分析之依赖注入(一)_第21张图片

return

将当前类和父类的注入点都找出来, 封装成一个对象, 把这个对象返回并且缓存, 存到外面的方法

Spring源码分析之依赖注入(一)_第22张图片

返回到上一个方法, 可以看到, 已经将返回值缓存到了 injectionMetadataCache中

Spring源码分析之依赖注入(一)_第23张图片

注入点进行注入

根据我们之前进行 Bean生命周期的分析, 接下来会执行 postProcessProperties方法

Spring源码分析之依赖注入(一)_第24张图片

取出注入点

在这个方法的第一行代码, 就是取出注入点, 具体代码如下所示, 之前已经全部注入完了, 这里就不详细讲了

Spring源码分析之依赖注入(一)_第25张图片

给注入点进行注入

如下图所示, 接下来获取到所有的注入点, 然后对其进行遍历

Spring源码分析之依赖注入(一)_第26张图片

遍历的 InjectedElement类, 是一个父类, 他的具体实现, 通过下图可以看到, 分别是方法和字段

Spring源码分析之依赖注入(一)_第27张图片

注意, 这里查看具体处理方法的时候, 不要直接点进 element.inject(target, beanName, pvs);方法, 因为我们是子类继承父类去实现的, 所以要去找子类的实现方法

Spring源码分析之依赖注入(一)_第28张图片

字段的注入实现

流程简单说明:

  • 找出注入点封装的字段
  • 获取字段的对象
  • 获取缓存, 我们没有, 直接进入else
  • 调用方法 resolveFieldValue
    • 根据字段的类型, 名字去找具体的值(下一篇文章详细讲解)
  • 找到之后通过反射为字段复制

字段的注入实现代码如下图所示:

Spring源码分析之依赖注入(一)_第29张图片

方法的注入实现

流程简单说明:

  • 如果pvs中已经有当前注入点的值了,则跳过注入
  • 找出方法对象
  • 获取缓存, 我们没有, 直接进入else
  • 根据 @Autowired注解的参数返回对象数组
  • 执行 method.invoke(bean, arguments);

方法的注入实现代码如下图所示:

Spring源码分析之依赖注入(一)_第30张图片

五、属性注入方法的执行

回到我们本标题的最开始, 看这个for遍历, 如下图所示

Spring源码分析之依赖注入(一)_第31张图片

在这个遍历过程中, 我们可以看到它执行了一个我们所熟悉的方法, 属性的注入

Spring源码分析之依赖注入(一)_第32张图片

六、属性赋值

可以看到, 在上面的代码分析过程中, 并没有实际的做属性赋值操作, 那么具体的属性赋值实际上是在该方法的最后一行

Spring源码分析之依赖注入(一)_第33张图片

这个方法实际上就是将 BeanDefinition中的属性和值设置到 Bean对象中, 感兴趣的可以去看一下

你可能感兴趣的:(spring,java,jvm)