Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入
Posted Javachichi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入相关的知识,希望对你有一定的参考价值。
在上一章节我们介绍了配置资源的加载和注册,此时,Spring容器已经管理了所有的Bean
定义相关数据。接下来,就是Bean
实例的创建和依赖注入(DI)了。DI(Dependency Injection)依赖注入是指对象是被动接受依赖类而不是自己主动去找,换句话说就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象的时候主动将它依赖的类注入给它。
寻找入口
Bean
实例的创建和依赖注入在以下两种情况下触发:
- 用户第一次调用
getBean()
方法时,IoC容器触发依赖注入。 - 当
Bean
的懒加载属性为false
时,在容器初始化时自动触发依赖注入。此时还是会调用到getBean()
方法。
因此,Bean
实例的创建和依赖注入的入口就在getBean()
方法中。在上一章节,我们讲过不管是ClassPathXmlApplicationContext
还是AnnotationConfigApplicationContext
都持有DefaultListableBeanFactory
实例。而它们的getBean()
就是直接调用了BeanFactory
的getBean()
方法。DefaultListableBeanFactory
的getBean()
才是真正的入口。getBean(String)
的实现在其抽象父类AbstractBeanFactory
中,源码如下:
实际调用的是doGetBean()
方法:
分析以上代码,我们发现有5个重点内容:
- 优先从单例缓存中获取
Bean
实例,如果获取到了,则无需再创建对象。 - 优先创建当前
Bean
依赖的所有Bean
。 - 如果是单例,创建对象之后会放到缓存中。
- 如果是原型,每次调用都会创建新的对象。
- 如果是自定义
scope
,则使用自定义scope
来管理创建对象的作用域。
不管以上哪种方式,最终创建都想都调用了createBean()
方法。
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
开始实例化
AbstractBeanFactory
的createBean()
最终调用了AbstractAutowireCapableBeanFactory
的实现,源码如下:
在doCreateBean()
的实现中,主要包含4个步骤:
- 创建
Bean
实例。 - 如果是单例,缓存单例对象,尽早持有引用,解决循环依赖问题。
- 填充
Bean
属性,依赖注入发生在这里。 - 初始化
Bean
,包括afterPropertiesSet()
方法和自定义的初始化方法。
下面我们分别介绍创建Bean
实例、填充Bean
属性以及初始化Bean
的具体实现。
创建Bean
实例
创建Bean
实例调用了createBeanInstance()
方法:
在createBeanInstance()
的实现中,主要根据不同情况来进行实例化,主要有以下四种情况:
- 调用创建Bean的回调方法实例化。
- 调用工厂方法实例化。
- 使用容器的自动装配特性,调用匹配的构造方法实例化
- 使用默认的无参构造方法实例化
前三种方式比较简单,直接调用匹配的方法实例化即可。但是对于我们最常使用的默认无参构造方法就需要使用相应的实例化策略(JDK的反射机制或者 CGLib)来进行实例化了,在方法getInstantiationStrategy().instantiate()
中就具体实现类使用相应的策略来实例化对象。源码如下:
最终调用了SimpleInstantiationStrategy
的instantiate()
方法:
如果Bean
没有方法被覆盖(表示不是子类),则使用JDK的反射机制进行实例化,否则,使用CGLib进行实例化。instantiateWithMethodInjection()
方法调用SimpleInstantiationStrategy
的子类 CGLibSubclassingInstantiationStrategy
使用CGLib来进行实例化,其源码如下:
CGLib 是一个常用的字节码生成器的类库,它提供了一系列API实现Java字节码的生成和转换功能。 我们在学习JDK的动态代理时都知道,JDK 的动态代理只能针对接口,如果一个类没有实现任何接口,要对其进行动态代理只能使用CGLib。
填充Bean
属性
在创建Bean
实例完成之后,接下来就是填充Bean
属性,即我们常说的依赖注入阶段了。populateBean()
源码如下:
在正式进行依赖注入之前,首先会调用Bean
实例化后置处理器,执行实例化的后置逻辑。对于自动注入的属性,分别可以根据名称或者类型完成注入。最后调用统一的applyPropertyValues()
方法完成属性赋值。
对属性的注入过程分以下两种情况:
- 属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。
- 属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的 属性值进行依赖注入。
对属性值的解析是在BeanDefinitionValueResolver
类中的resolveValueIfNecessary()
方法中进行的,对属性值的依赖注入是通过 bw.setPropertyValues()
方法实现的,在分析属性值的依赖注入之前,我们先分析一下对属性值的解析过程。
当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个Bean
实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标实例对象的属性上去,对属性进行解析的由resolveValueIfNecessary()
方法实现,其源码如下:
在上面的实现中,根据不同情况,Spring将引用类型,内部类以及集合类型等属性进行解析。解析完成后就可以进行依赖注入了。依赖注入的过程就是Bean
对象实例设置到它所依赖的Bean
对象属性上去。依赖注入本质就是给属性赋值,这里就详细展开了。
初始化Bean
前面已经创建好Bean
实例并且也完成了属性注入,下一步就是调用初始化方法做一些初始化操作了,源码如下:
初始化方法主要包含4部分内容:
- 调用
Aware
方法。Spring提供可很多Aware
接口,如果Bean
实现了Aware
接口,那么在初始化阶段就会调用某些Aware
方法。这里会调用的包括BeanNameAware
、BeanClassLoaderAware
和BeanFactoryAware
的方法。 - 调用
Bean
后置处理器,在初始化之前做一些处理。Spring提供可很多BeanPostProcessor
接口,用于在Bean
的各个生命周期中织入自定义逻辑。这里会执行应用的BeanPostProcessor
的postProcessBeforeInitialization()
方法。 - 调用初始化方法。包括
InitializingBean
接口的afterPropertiesSet()
以及自定义的初始化方法。 - 调用
Bean
后置处理器,在初始化之后做一些处理。这里会执行应用的BeanPostProcessor
的postProcessAfterInitialization()
方法。
总结
Spring容器启动主要分为两大阶段,第一阶段是配置资源的加载和注册,第二阶段是Bean
的实例化、依赖注入和初始化。在第一阶段,最关注的是BeanDefinition
,不管配置方式是xml还是注解,最终都会解析成BeanDefinition
对象,并将其注册到容器中。在第一阶段完成之后,容器中已经有了所有Bean
的配置信息。第二阶段则是依赖第一阶段处理好的BeanDefinition
,从而实现了Bean
的实例化、依赖注入和初始化操作。并且,在整个容器启动过程中,Spring提供了各种各样的回调入口,支持用户在各个执行阶段都可以注入自定义处理逻辑。
最后
秃头哥给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。
码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!
以上是关于Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入的主要内容,如果未能解决你的问题,请参考以下文章
spring源码阅读-- 容器启动之加载BeanDefinition
spring源码阅读-- 容器启动之BeanFactoryPostProcessor