Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入

Posted Javachichi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入相关的知识,希望对你有一定的参考价值。

在上一章节我们介绍了配置资源的加载和注册,此时,Spring容器已经管理了所有的Bean定义相关数据。接下来,就是Bean实例的创建和依赖注入(DI)了。DI(Dependency Injection)依赖注入是指对象是被动接受依赖类而不是自己主动去找,换句话说就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象的时候主动将它依赖的类注入给它

寻找入口

Bean实例的创建和依赖注入在以下两种情况下触发:

  1. 用户第一次调用getBean()方法时,IoC容器触发依赖注入。
  2. Bean的懒加载属性为false时,在容器初始化时自动触发依赖注入。此时还是会调用到getBean()方法。

因此,Bean实例的创建和依赖注入的入口就在getBean()方法中。在上一章节,我们讲过不管是ClassPathXmlApplicationContext还是AnnotationConfigApplicationContext都持有DefaultListableBeanFactory实例。而它们的getBean()就是直接调用了BeanFactorygetBean()方法。DefaultListableBeanFactorygetBean()才是真正的入口。getBean(String)的实现在其抽象父类AbstractBeanFactory中,源码如下: AbstractBeanFactory-getBean

实际调用的是doGetBean()方法: AbstractBeanFactory-doGetBean1

AbstractBeanFactory-doGetBean2

AbstractBeanFactory-doGetBean3

AbstractBeanFactory-doGetBean4

分析以上代码,我们发现有5个重点内容:

  1. 优先从单例缓存中获取Bean实例,如果获取到了,则无需再创建对象。
  2. 优先创建当前Bean依赖的所有Bean
  3. 如果是单例,创建对象之后会放到缓存中。
  4. 如果是原型,每次调用都会创建新的对象。
  5. 如果是自定义scope,则使用自定义scope来管理创建对象的作用域。

不管以上哪种方式,最终创建都想都调用了createBean()方法。

如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

开始实例化

AbstractBeanFactorycreateBean()最终调用了AbstractAutowireCapableBeanFactory的实现,源码如下: AbstractAutowireCapableBeanFactory-createBean

AbstractAutowireCapableBeanFactory-doCreateBean

doCreateBean()的实现中,主要包含4个步骤:

  1. 创建Bean实例。
  2. 如果是单例,缓存单例对象,尽早持有引用,解决循环依赖问题。
  3. 填充Bean属性,依赖注入发生在这里。
  4. 初始化Bean,包括afterPropertiesSet()方法和自定义的初始化方法。

下面我们分别介绍创建Bean实例、填充Bean属性以及初始化Bean的具体实现。

创建Bean实例

创建Bean实例调用了createBeanInstance()方法: AbstractAutowireCapableBeanFactory-createBeanInstance

createBeanInstance()的实现中,主要根据不同情况来进行实例化,主要有以下四种情况:

  1. 调用创建Bean的回调方法实例化。
  2. 调用工厂方法实例化。
  3. 使用容器的自动装配特性,调用匹配的构造方法实例化
  4. 使用默认的无参构造方法实例化

前三种方式比较简单,直接调用匹配的方法实例化即可。但是对于我们最常使用的默认无参构造方法就需要使用相应的实例化策略(JDK的反射机制或者 CGLib)来进行实例化了,在方法getInstantiationStrategy().instantiate()中就具体实现类使用相应的策略来实例化对象。源码如下: AbstractAutowireCapableBeanFactory-instantiateBean

最终调用了SimpleInstantiationStrategyinstantiate()方法: SimpleInstantiationStrategy-instantiate

如果Bean没有方法被覆盖(表示不是子类),则使用JDK的反射机制进行实例化,否则,使用CGLib进行实例化。instantiateWithMethodInjection()方法调用SimpleInstantiationStrategy的子类 CGLibSubclassingInstantiationStrategy使用CGLib来进行实例化,其源码如下: CglibSubclassingInstantiationStrategy-instantiate

CGLib 是一个常用的字节码生成器的类库,它提供了一系列API实现Java字节码的生成和转换功能。 我们在学习JDK的动态代理时都知道,JDK 的动态代理只能针对接口,如果一个类没有实现任何接口,要对其进行动态代理只能使用CGLib。

填充Bean属性

在创建Bean实例完成之后,接下来就是填充Bean属性,即我们常说的依赖注入阶段了populateBean()源码如下: AbstractAutowireCapableBeanFactory-populateBean

在正式进行依赖注入之前,首先会调用Bean实例化后置处理器,执行实例化的后置逻辑。对于自动注入的属性,分别可以根据名称或者类型完成注入。最后调用统一的applyPropertyValues()方法完成属性赋值。 AbstractAutowireCapableBeanFactory-applyPropertyValues

对属性的注入过程分以下两种情况:

  1. 属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。
  2. 属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的 属性值进行依赖注入。

对属性值的解析是在BeanDefinitionValueResolver类中的resolveValueIfNecessary()方法中进行的,对属性值的依赖注入是通过 bw.setPropertyValues()方法实现的,在分析属性值的依赖注入之前,我们先分析一下对属性值的解析过程。

当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个Bean实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标实例对象的属性上去,对属性进行解析的由resolveValueIfNecessary()方法实现,其源码如下: BeanDefinitionValueResolver-resolveValueIfNecessary

在上面的实现中,根据不同情况,Spring将引用类型,内部类以及集合类型等属性进行解析。解析完成后就可以进行依赖注入了。依赖注入的过程就是Bean对象实例设置到它所依赖的Bean对象属性上去。依赖注入本质就是给属性赋值,这里就详细展开了。

初始化Bean

前面已经创建好Bean实例并且也完成了属性注入,下一步就是调用初始化方法做一些初始化操作了,源码如下: AbstractAutowireCapableBeanFactory-initializeBean

初始化方法主要包含4部分内容:

  1. 调用Aware方法。Spring提供可很多Aware接口,如果Bean实现了Aware接口,那么在初始化阶段就会调用某些Aware方法。这里会调用的包括BeanNameAwareBeanClassLoaderAwareBeanFactoryAware的方法。
  2. 调用Bean后置处理器,在初始化之前做一些处理。Spring提供可很多BeanPostProcessor接口,用于在Bean的各个生命周期中织入自定义逻辑。这里会执行应用的BeanPostProcessorpostProcessBeforeInitialization()方法。
  3. 调用初始化方法。包括InitializingBean接口的afterPropertiesSet()以及自定义的初始化方法。
  4. 调用Bean后置处理器,在初始化之后做一些处理。这里会执行应用的BeanPostProcessorpostProcessAfterInitialization()方法。

总结

Spring容器启动主要分为两大阶段,第一阶段是配置资源的加载和注册,第二阶段是Bean的实例化、依赖注入和初始化。在第一阶段,最关注的是BeanDefinition,不管配置方式是xml还是注解,最终都会解析成BeanDefinition对象,并将其注册到容器中。在第一阶段完成之后,容器中已经有了所有Bean的配置信息。第二阶段则是依赖第一阶段处理好的BeanDefinition,从而实现了Bean的实例化、依赖注入和初始化操作。并且,在整个容器启动过程中,Spring提供了各种各样的回调入口,支持用户在各个执行阶段都可以注入自定义处理逻辑。

最后

秃头哥给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。

文档地址:一篇神文就把java多线程,锁,JMM,JUC和高并发设计模式讲明白了

码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!

以上是关于Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码分析专题——目录

spring源码阅读-- 容器启动之创建bean

spring源码阅读-- 容器启动之加载BeanDefinition

spring源码阅读-- 容器启动之BeanFactoryPostProcessor

#yyds干货盘点#Spring 源码阅读Spring IoCAOP 原理小总结

spring启动加载过程源码分析