十六、开启Bean加载
Spring IOC容器的作用:它会以某种方式加载 Configuration Metadata,将其解析注册到容器内部,然后回根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。具体流程分为两个阶段:
1、容器初始化阶段
- 首先,通过某种方式加载 Configuration Metadata (主要是依据 Resource、ResourceLoader 两个体系) 。
- 然后,容器会对加载的 Configuration MetaData 进行解析,并将解析的信息组装成 BeanDefinition 。
- 最后,将 BeanDefinition 保存注册到相应的 BeanDefinitionRegistry 中。
- 至此,Spring IoC 的初始化工作完成。
2、加载Bean阶段
- 经过容器初始化阶段后,应用程序中定义的 bean 信息已经全部加载到系统中了,当我们显示或者隐式地调用
BeanFactory.getBean(...)
方法时,则会触发加载 Bean 阶段。 - 在这阶段,容器会首先检查所请求的对象是否已经初始化完成了,如果没有,则会根据注册的 Bean 信息实例化请求的对象,并为其注册依赖,然后将其返回给请求方。
getBean()
方法
当我们显示或者隐式地调用 BeanFactory.getBean(...)
方法时,则会触发加载 Bean 阶段。
1 |
|
内部调用 doGetBean(String name, final Class requiredType, Object[] args, boolean typeCheckOnly)
方法。
doGetBean()
方法
getBean()
内部调用的是该方法,主要步骤总结在下方:
1 |
|
doGetBean()
的流程:
①、获取beanName
②、从缓存中或者实例工厂中获取 Bean 对象
③、原型模式依赖检查
④、从父类容器加载bean
⑤、标记当前bean为正在创建或者创建中
⑥、获取BeanDefinition
⑦、依赖bean处理
⑧、不同作用域的bean实例化
⑨、类型转换
获取beanName
1 |
|
该name
,不一定是beanName
,可能是aliasName
,也有可能是FactoryBean
。所以需要执行该方法对name
进行转换。
1 |
|
BeanFactoryUtils.transformedBeanName(name)
先调用BeanFactoryUtils.transformedBeanName(name)
方法,去除FactoryBean
的修饰符:
1 |
|
tip:假设配置了一个 FactoryBean 的名字为
"abc"
,那么获取 FactoryBean 创建的 Bean 时,使用"abc"
,如果获取 FactoryBean 本身,使用"$abc"
。
transformedBeanNameCache
集合的存在,是为了缓存转换后的结果。下次再获取相同的 name
时,直接返回缓存中的结果即可。
canonicalName(...)
其次执行canonicalName(String name)
:AbstractBeanFactory
继承了FactoryBeanRegistrySupport
,FactoryBeanRegistrySupport
继承了DefaultSingletonBeanRegistry
,DefaultSingletonBeanRegistry
继承了SimpleAliasRegistry
。
1 |
|
从缓存中或者实例工厂中获取 Bean 对象
相应代码
1 |
|
单例模式的 Bean 在整个过程中只会被创建一次。第一次创建后会将该 Bean 加载到缓存中。后面,在获取 Bean 就会直接从单例缓存中获取。
如果从缓存中得到了 Bean 对象,则需要调用 getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)
方法,对 Bean 进行实例化处理。因为,缓存中记录的是最原始的 Bean 状态,我们得到的不一定是我们最终想要的 Bean 。
原型模式依赖检查
1 |
|
Spring 只处理单例模式下得循环依赖,对于原型模式的循环依赖直接抛出异常。主要原因还是在于,和 Spring 解决循环依赖的策略有关。
- 对于单例( Singleton )模式, Spring 在创建 Bean 的时候并不是等 Bean 完全创建完成后才会将 Bean 添加至缓存中,而是不等 Bean 创建完成就会将创建 Bean 的 ObjectFactory 提早加入到缓存中,这样一旦下一个 Bean 创建的时候需要依赖 bean 时则直接使用 ObjectFactroy 。
- 但是原型( Prototype )模式,我们知道是没法使用缓存的,所以 Spring 对原型模式的循环依赖处理策略则是不处理。
从父类容器加载bean
1 |
|
如果当前容器缓存中没有相对应的 BeanDefinition 对象,则会尝试从父类工厂(parentBeanFactory
)中加载,然后再去递归调用 getBean(...)
方法。
标记当前bean为正在创建或者创建中
1 |
|
获取BeanDefinition
1 |
|
因为从 XML 配置文件中读取到的 Bean 信息是存储在GenericBeanDefinition 中的。但是,所有的 Bean 后续处理都是针对于 RootBeanDefinition 的,所以这里需要进行一个转换。转换的同时,如果父类 bean 不为空的话,则会一并合并父类的属性。
依赖bean处理
1 |
|
- 每个 Bean 都不是单独工作的,它会依赖其他 Bean,其他 Bean 也会依赖它。
- 对于依赖的 Bean ,它会优先加载,所以,在 Spring 的加载顺序中,在初始化某一个 Bean 的时候,首先会初始化这个 Bean 的依赖。
不同作用域的bean实例化
1 |
|
- Spring Bean 的作用域默认为 singleton 。当然,还有其他作用域,如 prototype、request、session 等。
- 不同的作用域会有不同的初始化策略。
类型转换
1 |
|
- 在调用
#doGetBean(...)
方法时,有一个 requiredTyp
e 参数。该参数的功能就是将返回的 Bean 转换为requiredType
类型。 - 当然就一般而言,我们是不需要进行类型转换的,也就是
requiredType
为空(比如#getBean(String name)
方法)。但有,可能会存在这种情况,比如我们返回的 Bean 类型为 String ,我们在使用的时候需要将其转换为 Integer,那么这个时候requiredType
就有用武之地了。当然我们一般是不需要这样做的。
总结
以上是对Spring Bean加载过程的概览,主要过程:
- 分析从缓存中获取单例 Bean ,以及对 Bean 的实例中获取对象。
- 如果从单例缓存中获取 Bean ,Spring 是怎么加载的呢?所以第二部分是分析 Bean 加载,以及 Bean 的依赖处理。
- Bean 已经加载了,依赖也处理完毕了,第三部分则分析各个作用域的 Bean 初始化过程。