Spring 是如何解决循环依赖问题的

 

1.循环依赖

首先我们要知道什么是Spring的循环依赖问题。假如我们有两个bean,A 和 B。他们的代码简单如下:

@Bean
public class A {
    @Autowire
    private B b;
}


@Bean
public class B {
    @Autowire
    private A a;
}

也就是需要在A中注入B,在B中注入A,那么Spring在创建A的时候会出现这种现象:创建A实例后,在依赖注入时需要B,然后就去创建B,这时候发现又需要依赖注入 A ,这样就导致了循环依赖。

2.Spring的解决方法

2.1 宏观思想

Spring对循环依赖的解决方法可以概括为 用三级缓存方式达到Bean提前曝光的目的

Spring创建的过程简单的可以简单概括为 实例化——>依赖注入——>初始化。而Spring解决循环依赖的方法就是在实例化之后,依赖注入之前,将实例化的对象放到缓存中进行提前曝光,后边的对象则在实例化前,先到缓存中查找有无对应的实例化对象即可,例如:

这个流程就是解决循环依赖的宏观流程,有了这个认识,我们接下来再来说说具体细节三级缓存,也就是上图中 将A放到缓存从缓存中获取A 的部分。

2.2 源码解析

1. 首先我们创建两个类如上边的 A 和 B

 

2.启动Spring容器,在创建A 的过程中会先来到 AbstractBeanFactory.doCreateBean 方法中找缓存(当然第一次找,肯定没有找不到,所以 sharedInstance = null)

3.所以不会走if里边的逻辑直接往下走,走到创建获取单例实例的位置(注意这里传入 getSingleton方法的第二个参数是一个方法的lamda表达式,后边有用),进入方法

4.到了 DefaultSingletonBeanRegistry.getSingleton 方法,走到标记部分,这里就是调用了传入的 lamda 表达式的方法,进入这个方法)

5.来到了 AbstractAutowireCapableBeanFactory.createBean 方法,其中重点方法是如下标记的方法,进入

6.还是原来的类,来到了doCreateBean 方法,这里先创建并获得实例对象

7.然后走到如下标记的位置,这里就是缓存三级缓存的代码部分,

8.具体如下标识,首先判断一级缓存有无对应实例(此时没有),接着将传入的 () -> getEarlyBeanReference(beanName, mbd, bean) 放到三级缓存 singletonFactories 中,这时候三级缓存就有对象了,此时的对象只是一个 带有方法

 () -> getEarlyBeanReference(beanName, mbd, bean) 的 objectFactory对象

9.放入三级缓存后,回到doCreateBean方法继续走,走到这里开始进行依赖注入

10.这里边后就需要对属性b进行注入,所以继续走前边一开始创建A的步骤一样去创建对象B,也是走到依赖注入这里,这时候要对B的属性A进行注入,所以又来到创建A的阶段 AbstractBeanFactory.doCreateBean,不过这次的 三级缓存 singletonFactories 已经有上一次放入的objectFactory了

11.进入这个方法,这里就是三级缓存拿数据的重点,其中需要说明下,三级缓存存的对象的 lamda 表达式 ( () -> getEarlyBeanReference(beanName, mbd, bean) ) 的方法,里边的逻辑上为判断对象需不需要代理,需要的话创建bean的代理并返回,不需要的话则直接返回 bean

12.所以接着就继续走下去,b能拿到对象a进行依赖注入,继续走完  AbstractAutowireCapableBeanFactory.createBean 方法(第5步),返回对象回到 DefaultSingletonBeanRegistry.getSingleton方法中(第4步),此时b的值已经有了,

继续走下去,将B放入一级缓存(单例缓存),同时移除二级缓存和三级缓存,逻辑在addSingleton()方法中,B创建完成,最后返回b对象给到一开始的a,a拿到b后就可以进行注入,注入后又将a放入一级缓存,移除二级缓存和三级缓存。至此A也创建好了。

以上就是Spring解决循环依赖的具体源码实现

3.思考

3.1为什么需要三级缓存,能否只用两级缓存?

我们假设有这样的代码:

@Transactional
public class A {
    Autowired
    B b;

    Autowired
    C c;
}

public class B {
    Autowired
    A a;
}

public class C {
    Autowired
    A a;
}

A加了事务注解,所以A肯定是要进行AOP代理的,这时候当A实例化出来对象,注入B的时候,需要实例化A,从三级缓存拿到A对象后执行生成A代理对象$A1(对应2.2步骤11),此时没有二级缓存的话,只能拿到$A1注入到B对象中;然后A在出入C的时候也是同样的步骤又生成一个A的代理对象$A2,$A2随后被注入到C中,这就会导致B和C中的属性A各不相同,所以必须要有第二级缓存,存放获得的代理。

(顺便一提,循环依赖情况下,如果A需要代理,A会在 对应2.2步骤11 时候便生成代理对象,此后在b和c都被注入到A后,A就不再继续去做AOP代理逻辑了,源码里边在创建代理前会做判断)

3.2 什么情况下Spring没法给我们解决循环依赖问题?

当循环依赖的时候,依赖的彼此都只提供带参数的构造方法的时候,如下

@Service
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

@Service
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

这时候会报异常,因为这时候在实例化A的时候就需要b了(仅提供一个有参构造方法,所以Spring只能调用这个有参构造方法进行实例化),而这时候去创建B,发现B也需要a,而a又还没实例化,所以没法实例化了

解决方法,加上注解 @Lazy

此时代表构造方法中的B是延时加载,所以构造的时候Spring先帮我们创建了B的代理对象,这时候A就可以实例化成功,此时再去实例化B,因为A已经实例化完成,那么B也可以实例化成功了。

  • 17
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值