Spring IOC容器中那些鲜为人知的细节

Posted 47号Gamer丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IOC容器中那些鲜为人知的细节相关的知识,希望对你有一定的参考价值。

通过前面对 Spring IOC 容器的源码分析,我们已经基本上了解了 Spring IOC 容器对 Bean 定义资源的定位、载入和注册过程,同时也清楚了当用户通过 getBean()方法向 IOC 容器获取被管理的 Bean时,IOC 容器对 Bean 进行的初始化和依赖注入过程,这些是 Spring IOC 容器的基本功能特性。Spring IOC 容器还有一些高级特性,如使用 lazy-init 属性对 Bean 预初始化、FactoryBean 产生或者修饰 Bean 对象的生成、IOC 容器初始化 Bean 过程中使用 BeanPostProcessor 后置处理器对 Bean 声明周期事件管理等。
 
关于延时加载
通过前面我们对 IOC 容器的实现和工作原理分析,我们已经知道 IOC 容器的初始化过程就是对 Bean定义资源的定位、载入和注册,此时容器对 Bean 的依赖注入并没有发生,依赖注入主要是在应用程序第一次向容器索取 Bean 时,通过 getBean()方法的调用完成。当 Bean 定义资源的<Bean>元素中配置了 lazy-init=false 属性时,容器将会在初始化的时候对所配置的 Bean 进行预实例化,Bean 的依赖注入在容器初始化的时候就已经完成。这样,当应用程序第一次向容器索取被管理的 Bean 时,就不用再初始化和对 Bean 进行依赖注入了,直接从容器中获取已经完成依赖注入的现成 Bean,可以提高应用第一次向容器获取 Bean 的性能。
 
1、refresh()方法
 
先从 IOC 容器的初始化过程开始,我们知道 IOC 容器读入已经定位的 Bean 定义资源是从 refresh()方法开始的,我们首先从 AbstractApplicationContext 类的 refresh()方法入手分析,源码如下:

在 refresh()方法中 ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();启动了 Bean 定义资源的载入、注册过程,而 finishBeanFactoryInitialization 方法是对注册后的 Bean定义中的预实例化(lazy-init=false,Spring 默认就是预实例化,即为 true)的Bean 进行处理的地方。
 
2、finishBeanFactoryInitialization 处理预实例化 Bean
 
当 Bean 定义资源被载入 IOC 容器之后,容器将 Bean 定义资源解析为容器内部的数据结构BeanDefinition 注册到容器中,AbstractApplicationContext 类中的 finishBeanFactoryInitialization()方法对配置了预实例化属性的 Bean 进行预初始化过程,源码如下:
ConfigurableListableBeanFactory 是 一 个 接 口 , 其 preInstantiateSingletons() 方 法 由 其 子 类DefaultListableBeanFactory 提供。
 
3、DefaultListableBeanFactory 对配置 lazy-init 属性单态 Bean 的预实例化
 

通过对 lazy-init 处理源码的分析,我们可以看出,如果设置了 lazy-init 属性,则容器在完成 Bean 定义的注册之后,会通过 getBean 方法,触发对指定 Bean 的初始化和依赖注入过程,这样当应用第一次向容器索取所需的 Bean 时,容器不再需要对 Bean 进行初始化和依赖注入,直接从已经完成实例化和依赖注入的 Bean 中取一个现成的 Bean,这样就提高了第一次获取 Bean 的性能。
 
关于 FactoryBean 和 BeanFactory
 
在 Spring 中,有两个很容易混淆的类:BeanFactory 和 FactoryBean。
BeanFactory:Bean 工厂,是一个工厂(Factory),我们 Spring IOC 容器的最顶层接口就是这个
BeanFactory,它的作用是管理 Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的
依赖。
FactoryBean:工厂 Bean,是一个 Bean,作用是产生其他 bean 实例。通常情况下,这种 Bean 没有什么特别的要求,仅需要提供一个工厂方法,该方法用来返回其他 Bean 实例。通常情况下,Bean 无须自己实现工厂模式,Spring 容器担任工厂角色;但少数情况下,容器中的 Bean 本身就是工厂,其作用是产生其它 Bean 实例。当用户使用容器本身时,可以使用转义字符”&”来得到 FactoryBean 本身,以区别通过 FactoryBean产生的实例对象和 FactoryBean 对象本身。在 BeanFactory 中通过如下代码定义了该转义字符:String FACTORY_BEAN_PREFIX = "&";如果 myJndiObject 是一个 FactoryBean,则使用&myJndiObject 得到的是 myJndiObject 对象,而不是 myJndiObject 产生出来的对象。
 
1、FactoryBean 源码:

2、AbstractBeanFactory 的 getBean()方法调用 FactoryBean: 
 
在前面我们分析 Spring IOC 容器实例化 Bean 并进行依赖注入过程的源码时,提到在 getBean()方法触发容器实例化 Bean 的时候会调用 AbstractBeanFactory 的 doGetBean()方法来进行实例化的过程,源码如下:

在 上 面 获 取 给 定 Bean 的 实 例 对 象 的 getObjectForBeanInstance() 方 法 中 , 会 调 用FactoryBeanRegistrySupport 类的 getObjectFromFactoryBean()方法,该方法实现了 Bean 工厂生产 Bean 实例对象。Dereference(解引用):一个在 C/C++中应用比较多的术语,在 C++中,”*”是解引用符号,而”&”是引用符号,解引用是指变量指向的是所引用对象的本身数据,而不是引用对象的内存地址。

3、AbstractBeanFactory 生产 Bean 实例对象
AbstractBeanFactory 类中生产 Bean 实例对象的主要源码如下: 

从上面的源码分析中,我们可以看出,BeanFactory 接口调用其实现类的 getObject 方法来实现创建Bean 实例对象的功能。
 
4、工厂 Bean 的实现类 getObject 方法创建 Bean 实例对象
 
FactoryBean 的实现类有非常多,比如:Proxy、RMI、JNDI、ServletContextFactoryBean 等等,FactoryBean 接口为 Spring 容器提供了一个很好的封装机制,具体的 getObject()有不同的实现类根据不同的实现策略来具体提供,我们分析一个最简单的 AnnotationTestFactoryBean 的实现源码:
其他的 Proxy,RMI,JNDI 等等,都是根据相应的策略提供 getObject()的实现。这里不做一一分析,这已经不是 Spring 的核心功能,感兴趣的小伙可以再去深入研究。
 
再述 autowiring
 
Spring IOC 容器提供了两种管理 Bean 依赖关系的方式:
1)、显式管理:通过 BeanDefinition 的属性值和构造方法实现 Bean 依赖关系管理。
2)、autowiring:Spring IOC 容器的依赖自动装配功能,不需要对 Bean 属性的依赖关系做显式的声明,只需要在配置好 autowiring 属性,IOC 容器会自动使用反射查找属性的类型和名称,然后基于属性的类型或者名称来自动匹配容器中管理的 Bean,从而自动地完成依赖注入。通过对 autowiring 自动装配特性的理解,我们知道容器对 Bean 的自动装配发生在容器对 Bean 依赖注入的过程中。在前面对 Spring IOC 容器的依赖注入过程源码分析中,我们已经知道了容器对 Bean 实例对象的属性注入的处理发生在 AbstractAutoWireCapableBeanFactory 类中的 populateBean()方法中,我们通过程序流程分析 autowiring 的实现原理:
 
1、AbstractAutoWireCapableBeanFactory 对 Bean 实例进行属性依赖注入
应用第一次通过 getBean()方法(配置了 lazy-init 预实例化属性的除外)向 IOC 容器索取 Bean 时,容器创 建 Bean 实 例 对 象 , 并 且 对 Bean 实 例 对 象 进 行 属 性 依 赖 注 入 ,AbstractAutoWireCapableBeanFactory 的 populateBean()方法就是实现 Bean 属性依赖注入的功能,其主要源码如下: 

2、Spring IOC 容器根据 Bean 名称或者类型进行 autowiring 自动依赖注入 
 

 

通过上面的源码分析,我们可以看出来通过属性名进行自动依赖注入的相对比通过属性类型进行自动依赖 注 入 要 稍 微 简 单 一 些 , 但 是 真 正 实 现 属 性 注 入 的 是 DefaultSingletonBeanRegistry 类 的registerDependentBean()方法。

3、DefaultSingletonBeanRegistry 的 registerDependentBean()方法对属性注入

通过对 autowiring 的源码分析,我们可以看出,autowiring 的实现过程:
a、对 Bean 的属性代调用 getBean()方法,完成依赖 Bean 的初始化和依赖注入。
b、将依赖 Bean 的属性引用设置到被依赖的 Bean 属性上。
c、将依赖 Bean 的名称和被依赖 Bean 的名称存储在 IOC 容器的集合中。
Spring IOC 容器的 autowiring 属性自动依赖注入是一个很方便的特性,可以简化开发时的配置,但是凡是都有两面性,自动属性依赖注入也有不足,首先,Bean 的依赖关系在 配置文件中无法很清楚地看出来,对于维护造成一定困难。其次,由于自动依赖注入是 Spring 容器自动执行的,容器是不会智能判断的,如果配置不当,将会带来无法预料的后果,所以自动依赖注入特性在使用时还是综合考虑。

以上是关于Spring IOC容器中那些鲜为人知的细节的主要内容,如果未能解决你的问题,请参考以下文章

Spring系列之IOC容器

Spring--作用IOC容器细节搭配环境Spring实验 (转)

Spring 5IOC 容器

spring ioc 容器概念

[Spring5]IOC容器_Bean管理注解方式_组件扫描配置细节

Spring核心技术IoC容器