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

本文深入探讨了Spring框架中的依赖注入,重点分析了@Bean注解的两种注入方式(AUTOWIRE_BY_NAME和AUTOWIRE_BY_TYPE),并详细阐述了@Autowired、@Resource注解的处理过程,包括查找注入点、属性注入的实现细节。文章适合对Spring源码感兴趣的开发者阅读。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

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

二、前置学习

依赖注入的几种方式

@Bean注解注入(已废弃)

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

自动注入

源码位置

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

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

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

三、@Bean注解注入分析

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

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

AUTOWIRE_BY_NAME

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

unsatisfiedNonSimpleProperties方法

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

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

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

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

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

简单类型如下图所示

进入循环

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

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

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

AUTOWIRE_BY_TYPE

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

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

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

代码如下图所示:

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

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

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

在上图中

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

寻找注入点

我们进入 AutowiredAnnotationBeanPostProcessor类中

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

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

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

点进这个方法内部

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

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

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

遍历当前 Bean所有的字段

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

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

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

static不进行注入

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

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

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

如果获取到相关的注解

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

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

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

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

遍历当前 Bean所有的方法

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

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

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

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

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

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

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

return

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

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

注入点进行注入

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

取出注入点

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

给注入点进行注入

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

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

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

字段的注入实现

流程简单说明:

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

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

方法的注入实现

流程简单说明:

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

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

五、属性注入方法的执行

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

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

六、属性赋值

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值