← 返回首页
Spring基础教程(二十)
发表时间:2022-08-15 23:43:16
循环依赖

1.什么是循环依赖

比如,A对象依赖了B对象,而B对象又依赖了A对象。

@Component
public class A {

    @Autowired
    private B   b ;

    public void fn(){
        System.out.println("Class A fn() is called...");
    }

    public void callOtherFn(){
        b.fn();
    }
}



@Component
public class B {

    @Autowired
    private A a;

    public void fn(){
        System.out.println("Class B fn() is called...");
    }

    public void callOtherFn(){
        a.fn();
    }
}

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。也可以使用new关键字正常地创建对象即可。但是,在Spring中循环依赖就是一个问题了,因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,最终由IOC容器注入,因此就会出现循环依赖问题。

2.如何解决循环依赖

Spring通过三级缓存来解决循环依赖问题,首先看下三级缓存是通用的说法。 - 一级缓存为:singletonObjects - 二级缓存为:earlySingletonObjects - 三级缓存为:singletonFactories

解决思路: A创建时--->需要B---->B去创建--->需要A,从而产生了循环依赖。如下图:

那么如何打破这个循环,加个中间人(缓存)

A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了,称之为:earlySingletonObjects),放入缓存后,再进行依赖注入,此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean),B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。 因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。

从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要singletonFactories呢?

这是难点,基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。因此出现了B依赖的A和最终的A不是同一个对象。

为了解决这个问题,因此出现了三级缓存singletonFactories,这个工厂的作用就是判断这个对象是否需要代理。

3.总结

1) singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean。 2) earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期 3) singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。 4) 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。