【Spring IOC】【四】容器源码解析- 循环依赖

1  前言

获取Bean我们大概都了解过了,这篇我们着重讲一下在获取bean的时候,出现的一个循环依赖的问题,这面试可以说是一道必问的题了,看的时候多想多看,别钻死牛角尖,我看的时候我陷入了一个牛角尖,我就在纠结为什么要用三个缓存来解决循环依赖呢,其实一个缓存也可以解决丫,干嘛要用三个呢。我总是陷入这个问题,其实别纠结人家为什么要用三个,这是人家的一个设计,确实一个缓存可以解决,但是你站在框架的角度,你要把AOP融进来,还要考虑你开发框架完要给别人用,使用者要使用AOP的入口设计,框架的纯粹度和优雅性是不是,可能正是因为这样的考虑,人家这么设计的,我猜的哈,有理解错的,请指正哈。

看过Spring IOC这块的源码,熟悉一些类以及前后方法关系的,因为东西太多,我会直截取关键的地方来说,否则你会不清楚来龙去脉没思路的。

那我们进入正题,什么是循环依赖,跟死锁好像有点像,就是你中有我 我中有你,这种关系是间接的或者直接的,打个比方:

A类里需要注入B类,B类里需要注入A类, 这就是直接性的循环依赖

A类里需要注入B类,B类里需要注入C类 C类里又需要注入A类, 这就是间接性的循环依赖

不管是直接还是间接,都是互相依赖导致的恰恰闭环出现的循环依赖,那么Spring是怎么解决的呢,我们看看。

2  依赖关系分类

在Spring里表达依赖关系有几种方式,Spring并不是对所有的方式都支持解决的这个你要明白,那么它给解决哪些,不给解决哪些呢(不给解决就需要你自己解除掉这种关系)?

要进行多例或者单例分类,根据注入方式分类:

  • depends-on 这种方式的都不支持,不管是单例的还是多例的,主要bean之间有depends-on导致的循环依赖,Spring都会直接抛异常  这个我们在getBean的时候说过。下边我再贴个图。
  • 构造器方式注入  这种方式的都不支持   这个好理解,这种方式会导致对象都创不出来,自己想想是不是 对象都实例化不完
  • setter方式注入    单例这种支持  但是多例的不支持,多例不会走三级缓存对象创建不出来
  • 注解方式注入      单例支持,多例的不支持

3  代码准备(边看源码边调试)

我们边看源码边拿一个例子来说,这样方便理解,我的类关系是这样的:

// A注入B B注入A
@Component
public class A {
    @Autowired
    private B b;

    public void say() {
        System.out.println("a");
    }

}
@Component
public class B {
    @Autowired
    private A a;

    public void say() {
        System.out.println("b");
    }
}

另外,这两个类我加了个AOP也

<!--通知-->
<bean id="advices" class="com.virtuous.demo.spring.LogAspect"/>
<!--aop配置-->
<aop:config proxy-target-class="true">
  <!--切面-->
  <aop:aspect ref="advices">
    <!--切点-->
    <aop:pointcut id="pointcur1" expression="execution( * com.virtuous.demo.spring.cycle.*.*(..))"/>
    <!--连接通知方法与切点-->
    <aop:before method="before" pointcut-ref="pointcur1"/>
  </aop:aspect>
</aop:config>

测试类:

public class ApplicationContextTest {

    @Test
    public void testFactoryBean() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring/spring-alias.xml");
        A a = applicationContext.getBean("a", A.class);
        B b = applicationContext.getBean("b", B.class);
        System.out.println(a);
        System.out.println(applicationContext.getBean("a"));
        System.out.println(b);
        System.out.println(applicationContext.getBean("b"));

    }
}

4  源码分析

好了,到这里我们要开始分析Spring怎么解决循环依赖的,我们会从关键的几个点去看:

(1)首先进来的就是a,先要创建a的bean

(2)createBeanInstancece实例化a后,进行判断是否提前暴露a,将ObjectFactory(A)即把实例A包装成ObjectFactory放入第3级缓存

 

(3)populateBean(A)即给a的属性赋值的时候,发现需要依赖B,那么就会getBean(B)

 (4)进行B的创建

 (5)createBeanInstancece实例化b后,进行判断是否提前暴露b,将ObjectFactory(B)即把实例B包装成ObjectFactory放入第3级缓存

 

(6)populateBean(B)即给b的属性赋值的时候,发现需要依赖A,从第3级缓存中取出a,并调用getObject()方法

 

 

 

 (7)AbstractAutoProxyCreator的getEarlyBeanReference方法会判断b是否需要代理,需要的话会返回代理对象,否则直接返回bean自己。

 

 (7)b创建完毕,清空第二、三级缓存,放入完整B到第一级缓存

 (8)回到a的创建a,也可以顺利初始化完毕,并清空自己的第二、三级缓存,把完整A放入第一级缓存中,完事。

 有没有看晕的,看晕的话就debug多看几次,多想想,注意第三级缓存是一个函数接口,注意嵌套,这个需要多看几遍源码,很多常用的方法,看几次以后就熟悉了,下边再画个图辅助理解一下:

5  小结

累了,歇会儿这家伙看的人脑袋高速运转啊,若文章有错误不妥之处,欢迎大家指出来。好了,本篇文章到此结束,谢谢大家的阅读。

posted @ 2023-02-16 23:38  酷酷-  阅读(26)  评论(0编辑  收藏  举报