本文作者:chenssy
出处:http://cmsblogs.com/?p=2806
在学习
Spring源码
的过程中发现的好站+好贴,感谢作者。Spring版本:Spring 5.0.6.RELEASE
(此图来自《Spring 揭秘》) Spring IOC
容器所起的作用如上图所示,它会以某种方式加载 Configuration Metadata
,将其解析注册到容器内部,然后回根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
Spring
在实现上述功能中,将整个流程分为两个阶段:容器初始化阶段和加载bean 阶段。
- 容器初始化阶段:首先通过某种方式加载 Configuration Metadata (主要是依据 Resource、ResourceLoader 两个体系),然后容器会对加载的 Configuration MetaData 进行解析和分析,并将分析的信息组装成 BeanDefinition,并将其保存注册到相应的 BeanDefinitionRegistry 中。至此,Spring IOC 的初始化工作完成。
- 加载 bean 阶段:经过容器初始化阶段后,应用程序中定义的 bean 信息已经全部加载到系统中了,当我们显示或者隐式地调用
getBean()
时,则会触发加载 bean 阶段。在这阶段,容器会首先检查所请求的对象是否已经初始化完成了,如果没有,则会根据注册的 bean 信息实例化请求的对象,并为其注册依赖,然后将其返回给请求方。至此第二个阶段也已经完成。
当我们显示或者隐式地调用 getBean()
时,则会触发加载 bean
阶段。如下:
public Object getBean(String name) throws BeansException { |
内部调用 doGetBean()
方法,其接受四个参数:
- name:要获取 bean 的名字
- requiredType:要获取 bean 的类型
- args:创建 bean 时传递的参数。这个参数仅限于创建 bean 时使用
- typeCheckOnly:是否为类型检查
这个方法的代码比较长,各位耐心看下:
protected <T> T doGetBean( |
代码是相当长,处理逻辑也是相当复杂,下面将其进行拆分阐述。
1.获取 beanName
final String beanName = transformedBeanName(name); |
这里传递的是 name,不一定就是 beanName,可能是 aliasName,也有可能是 FactoryBean,所以这里需要调用 transformedBeanName()
方法对 name 进行一番转换,主要如下:
protected String transformedBeanName(String name) { |
主要处理过程包括两步:
- 去除 FactoryBean 的修饰符。如果 name 以 “&” 为前缀,那么会去掉该 “&”,例如,
name = "&studentService"
,则会是name = "studentService"
。 - 取指定的 alias 所表示的最终 beanName。主要是一个循环获取 beanName 的过程,例如别名 A 指向名称为 B 的 bean 则返回 B,若 别名 A 指向别名 B,别名 B 指向名称为 C 的 bean,则返回 C。
2.从单例 bean 缓存中获取 bean 对应代码段如下:
Object sharedInstance = getSingleton(beanName); |
我们知道单例模式的 bean 在整个过程中只会被创建一次,第一次创建后会将该 bean 加载到缓存中,后面在获取 bean 就会直接从单例缓存中获取。如果从缓存中得到了 bean,则需要调用 getObjectForBeanInstance()
对 bean 进行实例化处理,因为缓存中记录的是最原始的 bean 状态,我们得到的不一定是我们最终想要的 bean。
3.原型模式依赖检查与 parentBeanFactory 对应代码段
if (isPrototypeCurrentlyInCreation(beanName)) { |
Spring 只处理单例模式下得循环依赖,对于原型模式的循环依赖直接抛出异常。主要原因还是在于 Spring 解决循环依赖的策略有关。对于单例模式 Spring 在创建 bean 的时候并不是等 bean 完全创建完成后才会将 bean 添加至缓存中,而是不等 bean 创建完成就会将创建 bean 的 ObjectFactory 提早加入到缓存中,这样一旦下一个 bean 创建的时候需要依赖 bean 时则直接使用 ObjectFactroy。但是原型模式我们知道是没法使用缓存的,所以 Spring 对原型模式的循环依赖处理策略则是不处理(关于循环依赖后面会有单独文章说明)。 如果容器缓存中没有相对应的 BeanDefinition 则会尝试从父类工厂(parentBeanFactory)中加载,然后再去递归调用 getBean()
。
4. 依赖处理 对应源码如下:
String[] dependsOn = mbd.getDependsOn(); |
每个 bean 都不是单独工作的,它会依赖其他 bean,其他 bean 也会依赖它,对于依赖的 bean ,它会优先加载,所以在 Spring 的加载顺序中,在初始化某一个 bean 的时候首先会初始化这个 bean 的依赖。
作用域处理 Spring bean 的作用域默认为 singleton,当然还有其他作用域,如prototype、request、session 等,不同的作用域会有不同的初始化策略。
类型转换 在调用 doGetBean()
方法时,有一个 requiredType 参数,该参数的功能就是将返回的 bean 转换为 requiredType 类型。当然就一般而言我们是不需要进行类型转换的,也就是 requiredType 为空(比如 getBean(String name)
),但有可能会存在这种情况,比如我们返回的 bean 类型为 String,我们在使用的时候需要将其转换为 Integer,那么这个时候 requiredType 就有用武之地了。当然我们一般是不需要这样做的。 至此 getBean()
过程讲解完了。后续将会对该过程进行拆分,更加详细的说明,弄清楚其中的来龙去脉,所以这篇博客只能算是 Spring bean 加载过程的一个概览。
拆分主要是分为三个部分:
- 分析从缓存中获取单例 bean,以及对 bean 的实例中获取对象
- 如果从单例缓存中获取 bean,Spring 是怎么加载的呢?所以第二部分是分析 bean 加载,以及 bean 的依赖处理
- bean 已经加载了,依赖也处理完毕了,第三部分则分析各个作用域的 bean 初始化过程。