概述
在介绍 Spring 是如何解决循环依赖之前,先介绍一下什么是 Bean 的循环依赖,下面通过案例介绍一下 Bean 的循环依赖过程和怎样解决。
setter方式单例
我们分别创建两个类,A 和 B 它们之间相互依赖,如下代码
A 类
@Component
public class A {
private B b;
public A( B b){
this.b = b;
}
public void getB(){
System.out.println(b);
}
}
B 类
@Component
public class B {
private A a;
public B(A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
SpringCircleConfig 类
@Configuration
@ComponentScan("com.zlp.spring.circle.set")
public class SpringCircleConfig {
}
测试类
public class AnnotationSetCircleTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);
A a = ac.getBean("a",A.class);
System.out.println(a.toString());
a.getB();
}
}
首先回忆一下之前介绍的 Spring 初始化过程的内容,在 Spring 初始化 Bean 的过程中,有一个重要很重要的步骤就是属性赋值。这个属性赋值的过程简单来讲就是获取每一个Bean对象内部的属性,然后通过反射把属性值赋给这个Bean对象。对于基本类型的值,例如String、Integer等,很简单,直接解析赋值即可。
对于对象类型的值,例如上述示例中,它的处理过程是什么呢?
第一步:A对象初始化完毕,此时开始进行属性填充,发现 A中需要注入B,则按照创建Bean的流程去创建B;
第二步:在创建 B 的过程中,首先去初始化 B,然后进行属性填充,发现 B 中需要注入 A,则按照创建Bean的流程在去创建A;
按照上述的过程会发现创建带有依赖关系的 Bean 的过程就会变成一个死循环,一直转圈。但是上述的示例可以正常运行,这是因为 Spring 中通过引入了早期对象以及使用多级缓存解决了这个问题。
整体的工作流程如下
早期对象
什么是早期对象呢?
早期对象指的是 Bean 的一个半成品。更确切的说,就是实例化完成(没有初始化),并执行了一部分后置处理器,但是还未填充属性的对象,这个对象不能直接使用。例如对于上例中的 A 对应的早期对象其实就是一个空的 A 实例,如下:
如何使用这个早期对象?
这个早期对象 A 创建出来之后,会被保存在一个地方【Spring的二级缓存中】。在进行 B 实例的属性赋值过程中,发现依赖了A这个对象,然后就会去找A对象。没找到初始化完成的A对象会再去找这个早期的A对象,找到之后,直接完成B实例的属性赋值,将这个没有初始化完成的 A 对象赋值给B中的 a 属性,此时,B完成属性填充。
然后 A 中依赖了 B,A 属性填充过程中同样会将未初始化完成的B对象填充给 b 属性,至此,完成依赖Bean的属性填充。
使用半成品的对象进行属性填充会有问题吗?
不会有问题。在属性填充过程完成之后,会进行 bean 的初始化过程,初始化过程中,初始化的 bean对象就是这个半成品对象,因为属性赋值的过程其实就是把对象的引用赋值给依赖Bean的成员变量【反应在内存中就是同一个地址引用】。所以当这个对象被初始化之后,之前已经完成赋值的半成品对象自然就会变成初始化完成的对象。
多级缓存
底层代码是通过三级缓存实现,对应三个 Map,代码过于繁多,具体可参考:
DefaultSingletonBeanRegistry.java 类中
/**
* Cache of singleton objects: bean name to bean instance.
* 一级缓存:单例对象的缓存,也被称作单例缓存池
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* Cache of early singleton objects: bean name to bean instance.
* 二级缓存:提前曝光的单例对象的缓存,用于检测循环引用
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* Cache of singleton factories: bean name to ObjectFactory.
* 三级缓存:保存对单实例bean的包装对象
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 按照注册顺序所保存的已经注册的单例对象的名称
*/
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
/**
* Add the given singleton object to the singleton cache of this factory.
* <p>To be called for eager registration of singletons.
* @param beanName the name of the bean
* @param singletonObject the singleton object
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 将创建好的单实例bean放入到单例缓存池中
this.singletonObjects.put(beanName, singletonObject);
// 从三级缓存中删除
this.singletonFactories.remove(beanName);
// 从二级缓存中删除(早期对象:已经实例化,但是未完成属性赋值的对象)
this.earlySingletonObjects.remove(beanName);
// 保存到已注册单实例Bean名称集合中
this.registeredSingletons.add(beanName);
}
}
/**
* 添加三级环境
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 如果当前的单实例缓存池中还没有beanName对应的单实例bean
if (!this.singletonObjects.containsKey(beanName)) {
// 将当前beanName对应的ObjectFactory放入到三级缓存singletonFactories中
this.singletonFactories.put(beanName, singletonFactory);
// 从早期的单例对象缓存中移除beanName对应的bean实例
this.earlySingletonObjects.remove(beanName);
// 将当前的beanName保存到已经注册的bean对应的Set集合中,标识其已经注册过
this.registeredSingletons.add(beanName);
}
}
}
@Override
@Nullable
public Object getSingleton(String beanName) {
// 从缓存中获取单实例Bean
return getSingleton(beanName, true);
}
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
// ******该段代码是 Spring 解决循环引用的核心代码******
//
// 解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),
// 并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存),
// 通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,
// 从而避免出现循环引用导致的死循环。
//
// 这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,
// 该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。
// 另外这个代码块中引进了4个重要缓存:
// singletonObjects 缓存:beanName -> 单例 bean 对象。
// earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。
// singletonFactories 缓存:beanName -> ObjectFactory。
// singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。
// singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “三级缓存” 的概念。
/**
* 注意:
* (1)通过 setter 注入方式产生的循环引用是可以通过以上方案解决的。
* (2)构造器注入方式产生的循环引用无法解决,因为无法实例化出 early singleton bean 实例。
* (3)非单例模式的循环引用也无法解决,因为 Spring 框架不会缓存非单例的 bean 实例。
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 根据beanName从单实例对象缓存中获取单例对象(singletonObjects为一个ConcurrentHashMap,就是用来保存所有的单实例Bean的,
// key:beanName value:beanInstance) 相当于一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
// 如果缓存中不存在,而且beanName对应的单实例Bean正在创建中.
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 加锁操作.
synchronized (this.singletonObjects) {
// 从早期单实例对象缓存中获取单例对象(之所以称为单实例早期对象,
// 是因为earlySingletonObjects里面的对象都是通过提前曝光的ObjectFactory创建出来的,还未进行属性的填充)
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果早期单实例对象缓存中没有,而且允许创建早期单实例对象引用
if (singletonObject == null && allowEarlyReference) {
// 则从单例工厂缓存中获取BeanName对应的单例工厂.
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 如果存在着单例对象工厂,则通过工厂创建一个单例对象,
// 调用的是:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))中的拉姆达表达式
singletonObject = singletonFactory.getObject();
// 将通过单例对象工厂创建的单例对象放入到早期单例对象缓存中,这个早期对象指的是一个空的未完成属性赋值和初始化的对象。
this.earlySingletonObjects.put(beanName, singletonObject);
// 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放入到earlySingletonObjects缓存中了,
// 所以,后续通过beanName获取单例对象,可以通过earlySingletonObjects缓存获取到,不需要再用到该单例工厂.
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
/**
* Return the (raw) singleton object registered under the given name,
* creating and registering a new one if none registered yet.
* @param beanName the name of the bean
* @param singletonFactory the ObjectFactory to lazily create the singleton
* with, if necessary
* @return the registered singleton object
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// 校验bean的名称,不能为空
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// 首先从单例缓存池singletonObjects【Map<String,Object>】中尝试获取单实例bean
Object singletonObject = this.singletonObjects.get(beanName);
// 如果未获取到,则通过如下的过程去创建单实例bean
if (singletonObject == null) {
/** 如果当前bean工厂中的实例bean正在被销毁,则不允许执行bean的创建过程 */
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
/** 创建单实例bean之前的检查,根据名称校验当前的单实例bean是否正在创建中. */
beforeSingletonCreation(beanName);
boolean newSingleton = false;
/** 初始化用来保存异常信息的Set集合 */
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
/** 回调ObjectFactory的getObject方法,进行单实例Bean的创建. */
singletonObject = singletonFactory.getObject();
/** 标注单实例bean创建成功 */
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
/** 创建完成之后,将bean名称从检查列表中删除. */
afterSingletonCreation(beanName);
}
if (newSingleton) {
/** 如果bean创建成功,将其加入单实例bean的map中 */
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
}
具体调用方法 AbstractBeanFactory.doGetBean方法中的 getSingleton(beanName)方法。
三级缓存对象
- singletonObjects:一级缓存,也被称为单实例缓存池,用来缓存所有初始化完成的Bean;
- earlySingletonObjects:二级缓存,用来保存早期对象,这个早期对象就是Bean的一个半成品对象,只完成了实例化化,未进行属性填充和初始化的对象;
- singletonFactories:三级缓存,用来保存获取早期对象的一个回调函数,通过这个回调函数,可以获取到未初始化完成的早期对象;
查询流程
- 根据Bean的名称首先查询一级缓存,查询到直接返回;
- 未查询到单实例Bean而且允许使用早期对象,则查询二级缓存,查询到则直接返回;
- 二级缓存中未查询到早期对象,则通过回调获取早期对象的方法获取早期对象,然后保存到二级缓存中并返回。
获取早期对象的实现可参考:
AbstractAutowireCapableBeanFactory.doCreateBean 中的 getEarlyBeanReference 方法。
获取 bean 调用流程方法
构造器参数循环依赖
构造器循环依赖
通过使用早期对象以及多级缓存解决了这种通过set方法注入时存在的循环依赖问题。但是通过构造器注入的方式无法解决,因为通过构造器的方式无法产生早期对象。如下示例:
A类
@Component
public class A {
private B b;
public A(@Lazy B b){
this.b = b;
}
public void getB(){
System.out.println(b);
}
}
B类
@Component
public class B {
private A a;
public B(@Lazy A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
测试类不变,运行之后,会提示如下异常:
BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
解决方式
构造器方式注入产生的循环依赖,可以通过指定 Bean 的延迟加载,先注入代理对象,然后在需要使用这个bean的时候再去真实创建,如下:
A类
@Component
public class A {
private B b;
public A(@Lazy B b){
this.b = b;
}
public void getB(){
System.out.println(b);
}
}
B类
@Component
public class B {
private A a;
public B(@Lazy A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
通过其他例如 @PostConstruct,或者使用后置处理器的方式也可以解决,但是如果真有这种循环依赖问题,建议还是使用最简单,也最容易理解的set方式去注入,因为这种方式框架本身就已经解决了这种问题。
setter方式原型,prototype
我们在A类和B类中,添加 @Scope(value = "prototype") 注解
@Component
@Scope(value = "prototype")
public class A {
@Autowired
private B b;
public void getB(){
System.out.println(b);
}
}
scope="prototype" 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。
测试从新启动报错
Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a';
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
为什么原型模式就报错了呢 ?
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
几个 QA?
三级缓存放流程
QA 问题
1、三级缓存存对象,在获取数据的时候什么顺序来获取的
先获取一级缓存,没有在获取二级缓存,没有再获取三级缓存,所以当前面的缓存中存在了对象
级缓存。
2、一级能解决循环依赖问题
有一级缓存,那么成品对象和半成品对象会放到一起,这个是没办法区分了,所以需要两个缓存来分别存放不同状态的对象,一级缓存放成品,二级缓存放半成品。
3、如果只有二个缓存,能否解决循环依赖问题?
在刚刚的整个流程中,三级缓存一共出现了几次? getsingleton, doCreateBean
如果对象的创建过程中不包含aop,那么二级缓存就可以解决循环依赖问题,但是包含aop的操作,
循环依赖问题是解决不了的。
4、为什么添加了aop的操作之后就需要添加三级缓存来解决这个问题?三级缓存加了什么操作?
添加了一个 getEarlyBeanReference 的方法在创建代理对象的时候,
是否需要生成原始对象?
需要当创建完成原始对象之后,后续有霃要创建代理对象,那么对象在引用的时候应该使用哪一个
换句话说,就是一个 beanName对应有两个对象,(原始对象和代理对象在整个容器中,有且仅能有一个同名的对象,当需要生成代理对象的时候,就要把代理对象覆盖原
程序是怎么知道在什么时候要进行代理对象的创建的呢?
需要一个类似于回调的接口判断,当需要第一次对外暴露使用的时候,来判断当前对象是否需要去
创建代理对象, getEarlyBeanReference 方法的判断,如果需要代理就返回代理对象,如果没有代
理就返回原始对象。
参数文档