主要讲解了在依赖注入过程中, 注入点的获取和属性的注入。
使用以下这种方式去注入, 源码分析会用到
实例化Bean方法: AbstractAutowireCapableBeanFactory.doCreateBean(), 在这个方法里面有一个属性填充的方法:
doCreateBean没印象或者不知道的, 建议从头开始看源代码, 从头走一遍, 才更清晰明了
点进去, 这个方法就是依赖注入相关源代码
在我们的源码方法 populateBean里面, 有一段这样的代码
如果我们使用 @Bean(autowire = Autowire.BY_TYPE) 这种方式去注入的话, 那么就会进入上图中红框位置的 if判断
假设我们使用的是 @Bean(autowire = Autowire.BY_NAME)方式, 那么就会进入以下方法
在上面的 autowireByName方法中, 又是通过了 unsatisfiedNonSimpleProperties(mbd, bw);方法去获取 Bean中能进行自动注入的属性名, 我们进入这个方法看一下
其中画红框的, 叫做属性描述器, 是由java.beans提供的, 在这里获取当前 Bean的类里面具体的属性和其getter, setter方法
当一个属性是 private作用域且有对应的 getter和 setter方法, 那么 java就认为他是一个真正的属性
接下来的流程判断什么样的属性能进行自动注入?:
简单类型如下图所示
执行完 unsatisfiedNonSimpleProperties方法获取到当前 Bean中能进行自动注入的属性名之后进入循环遍历
进入这个循环之后的简单流程如下:
AUTOWIRE_BY_TYPE实际上和 AUTOWIRE_BY_NAME是一样的流程, 代码就不详细说明了, 流程如下:
resolveDependency方法是比较重要的, 后续会专门讲一下
代码如下图所示:
如图所示, 我们是执行了第二个红框内的方法去处理 @Autowired, @Resource等注解的, 通过上面第一个红框也可以看到, 他具体是通过后置处理器来实现的
我们进入第一个红框内的 InstantiationAwareBeanPostProcessor接口, 去看他具体的实现
在上图中
我们进入 AutowiredAnnotationBeanPostProcessor类中
在这个类里面有一个 postProcessMergedBeanDefinition方法
通过之前看源码的可知, 我们会先执行postProcessMergedBeanDefinition这个方法, 继续看里面的方法
根据当前 Bean的类去找注入点(属性或者某个方法上面加了 @Autowired注解), 具体方法是 buildAutowiringMetadata(clazz);
点进这个方法内部
例如: 如果一个Bean的类型是String, 那么则根本不需要进行依赖注入
这个方法里面就是各种方法跳转, 就不贴图了, 最后会进入下面这个方法, 大概意思是如果类名字是以 java.
开头的话就不用找注入点了
这里有一个lambda表达式, 他会去判断field上是否存在@Autowired、@Value、@Inject中的其中一个, 具体实现如下
如上图所示, 他会遍历一个 autowiredAnnotationTypes的 Set集合, 这个列表所属的类有一个构造方法, 在构造方法的内部为这个集合添加了 @Autowired注解和 @Value注解
在上图的 findAutowiredAnnotation()方法中, 它会去遍历当前类上的所有注解, 有没有刚刚配置在 Set集合中的注解
如图所示, 需要注意的是, 如果当前的属性是 static的, 那么直接return掉, 不进行属性的注入
为什么一个字段是static的, 不将其当做注入点
如果我们的 Bean对象不是单例的, 而是多例或者其他类型的, 那么获取一次Bean都会将其重新创建一遍, 这不符合我们对 static静态资源的预期
如果获取到相关的注解了, 那么继续往下走,会先执行一个方法, 然后把当前这个字段设置为一个注入点
在我们的 @Autowired注解中, 可以进行配置 required, 这个值默认是true的, 他的含义是: 如果在依赖注入的时候, 没有找到这个 Bean是会报错的, 如果设置为 false之后, 在依赖注入期间没有找到这个 Bean就不会报错了
下面的代码, 就是对 @Autowired注解属性配置的单独处理
boolean required = determineRequiredStatus(ann);
复制代码
接着往下走, 就是遍历当前 Bean所有的方法了
还是一样的, 简单流程如下:
桥接方法, 是专门用来处理一些特殊的情况的
如下图所示, 如果是用以下这种方式编写代码, 那么在字节码文件中就会发现两个 setOrderService方法, 这个时候就需要桥接方法帮助我们找到原方法
注意, 我们这个方法是一个 do while循环, 先执行当前 Bean再去遍历 父Bean
将当前类和父类的注入点都找出来, 封装成一个对象, 把这个对象返回并且缓存, 存到外面的方法
返回到上一个方法, 可以看到, 已经将返回值缓存到了 injectionMetadataCache中
根据我们之前进行 Bean生命周期的分析, 接下来会执行 postProcessProperties方法
在这个方法的第一行代码, 就是取出注入点, 具体代码如下所示, 之前已经全部注入完了, 这里就不详细讲了
如下图所示, 接下来获取到所有的注入点, 然后对其进行遍历
遍历的 InjectedElement类, 是一个父类, 他的具体实现, 通过下图可以看到, 分别是方法和字段
注意, 这里查看具体处理方法的时候, 不要直接点进 element.inject(target, beanName, pvs);方法, 因为我们是子类继承父类去实现的, 所以要去找子类的实现方法
流程简单说明:
字段的注入实现代码如下图所示:
流程简单说明:
方法的注入实现代码如下图所示:
回到我们本标题的最开始, 看这个for遍历, 如下图所示
在这个遍历过程中, 我们可以看到它执行了一个我们所熟悉的方法, 属性的注入
可以看到, 在上面的代码分析过程中, 并没有实际的做属性赋值操作, 那么具体的属性赋值实际上是在该方法的最后一行
这个方法实际上就是将 BeanDefinition中的属性和值设置到 Bean对象中, 感兴趣的可以去看一下