Spring-IOC-实现原理详解-2-Bean实例化
前言
本文主要研究如何从IOC容器已有的BeanDefinition信息,实例化出Bean对象。
这里还会包括三块重点内容:
- BeanFactory中getBean的主体思路
- Spring如何解决循环依赖问题
- Spring中Bean的生命周期
一、BeanFactory中getBean的主体思路
我们知道BeanFactory定义了Bean容器的规范,其中包含根据bean的名字、Class类型和参数等来得到bean实例。
1 | // 根据bean的名字和Class类型等来得到bean实例 |
研究源码之前的初步思考
了解过IoC容器初始化的流程,最终将Bean的定义即BeanDefinition放到beanDefinitionMap中,本质上是一个ConcurrentHashMap<String, Object>
,并且BeanDefinition接口中包含了这个类的Class信息以及是否是单例等。
这样我们初步有了实现Object getBean(String name)
这个方法的思路:
- 从beanDefinitionMap通过beanName获得BeanDefinition
- 从BeanDefinition中获得beanClassName
- 通过反射初始化beanClassName的实例instance
- 构造函数从BeanDefinition的**getConstructorArgumentValues()**方法获取
- 属性值从BeanDefinition的**getPropertyValues()**方法获取
- 返回beanName的实例instance
- 由于BeanDefinition还有单例的信息,如果是无参构造函数的实例还可以放在一个缓存中,这样下次获取这个单例的实例时只需要从缓存中获取,如果获取不到再通过上述步骤获取。
(PS:如上只是我们初步的思路,而Spring还需要考虑各种设计上的问题,比如beanDefinition中其它定义,循环依赖等;所以来看下Spring是如何是如何实现的)
Spring中getBean的主体思路
BeanFactory实现getBean方法在AbstractBeanFactory中,这个方法重载都是调用doGetBean方法进行实现的:
1 | public Object getBean(String name) throws BeansException { |
doGetBean
1 | // 版本:spring-beans-5.2.9-RELEASE |
这段代码很长,主体逻辑如下:
- 解析bean的真正name,如果bean是工厂类,name前缀会加&,需要去掉
- 先从单例缓存中尝试获取
getSingleton(beanName)
- 如果单例缓存中有 &&
args == null
:1
2
3
4
5
6
7
8
9
10
11
12
13
14// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}- 如果bean单例正在创建中,打印日志
- bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
- 如果单例缓存中没有:
- 如果bean实例正在创建中,则直接抛出异常
1
2
3
4
5// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} - 如果bean definition 不存在于当前bean工厂中,则委派给父Bean工厂获取
- 标记这个beanName的实例正在创建
- 确保它的依赖也被初始化
- 真正创建
- 单例时
- 原型时
- 根据bean的scope创建
- 如果bean实例正在创建中,则直接抛出异常
- 如果单例缓存中有 &&
二、Spring如何解决循环依赖问题
首先需要说明:
- Spring只是解决了单例模式下setter注入的循环依赖问题;
- Spring为了解决单例的循环依赖问题,使用了三级缓存。
Spring单例模式下的setter注入和三级缓存
先来看下这三级缓存
1 | // public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry |
- 第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象;
- 第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象;
- 第三层缓存(singletonFactories): 单例工厂的缓存
1 | // DefaultSingletonBeanRegistry#getSingleton |
- allowEarlyReference
是否容许从singletonFactories中经过getObject拿到对象 - isSingletonCurrentlyInCreation()
判断当前单例bean是否正在建立中,也就是没有初始化完成。
(好比A的构造器依赖了B对象因此得先去建立B对象, 或则在A的populateBean过程当中依赖了B对象,得先去建立B对象,这时的A就是处于建立中的状态。)
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。若是获取不到,而且对象正在建立中,就再从二级缓存earlySingletonObjects中获取。若是仍是获取不到且容许singletonFactories经过getObject()获取,就从**三级缓存singletonFactory.getObject()(三级缓存)**获取,若是获取到了则将其put进二级缓存。
从上面三级缓存的分析,咱们能够知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。
这个cache的类型是ObjectFactory,定义以下:
1 | public interface ObjectFactory<T> { |
在bean建立过程当中,有两处比较重要的匿名内部类实现了ObjectFactory接口。
- 一处是Spring利用其建立bean的时候
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// AbstractBeanFactory#doGetBean
// 1、创建Bean实例:单例
if (mbd.isSingleton()) {
// 此处lambda表达式为ObjectFactory的实现
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} - 另外一处就是:AbstractAutowireCapableBeanFactory#doCreateBean#addSingletonFactory
doCreateBean bean的创建
- 在 doCreateBean 方法中包括的内容较多,但核心主要是创建实例、加入缓存以及最终进行属性填充,属性填充就是把一个bean的各个属性字段涉及到的类填充进去。
- createBeanInstance,创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回
- addSingletonFactory,添加 bean 工厂对象到 singletonFactories 缓存中
- getEarlyBeanReference,获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 bean。
- populateBean,填充属性,解析依赖关系。也就是从这开始去找寻 A 实例中属性 B,紧接着去创建 B 实例,最后在返回回来。
- initializeBean(beanName, exposedObject, mbd):完成 bean 的属性填充注入后,进一步初始化 bean,在此过程中产生代理对象。此时 bean 的创建工作正式完成,已经可以在项目中使用了
1 | addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); |
Spring为何不能解决非单例属性之外的循环依赖?
Spring是借助单例的三级缓存来解决单例的setter注入的循环依赖。
Spring为什么不能解决构造器的循环依赖?
构造器注入形成的循环依赖: 也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。
Spring解决循环依赖主要是依赖三级缓存,但是在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决。
Spring为什么不能解决prototype作用域循环依赖?
这种循环依赖同样无法解决,因为spring不会缓存‘prototype’
作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的。
Spring为什么不能解决多例的循环依赖?
多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。
其它循环依赖如何解决?
- 生成代理对象产生的循环依赖
- 使用@Lazy注解,延迟加载
- 使用@DependsOn注解,指定加载先后关系
- 修改文件名称,改变循环依赖类的加载顺序
- 使用@DependsOn产生的循环依赖
这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。 - 多例循环依赖
这类循环依赖问题可以通过把bean改成单例的解决。 - 构造器循环依赖
这类循环依赖问题可以通过使用@Lazy注解
解决。
三、Spring中Bean的生命周期
Spring 只帮我们管理单例模式 Bean 的完整生命周期;对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。
而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。
了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。这种时刻可能有很多,但一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。