Spring循环依赖

什么是循环依赖?

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A
在这里插入图片描述
通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景

在这里插入图片描述
也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题

两种注入方式对循环依赖的影响:
我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题

spring容器循环依赖报错演示BeanCurrentlylnCreationException:

循环依赖现象在Spring容器中 注入依赖的对象,有2种情况:

1.构造器方式注入依赖
ServiceA:

import org.springframework.stereotype.Component;

@Component
public class ServiceA {
    private ServiceB serviceB;
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

ServiceB:

import org.springframework.stereotype.Component;
@Component
public class ServiceB {

    private ServiceA serviceA;
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

ClientConstructor:

/**
 * 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
 *
 * 测试后发现,构造器循环依赖是无法解决的
 */
public class ClientConstructor {
    public static void main(String[] args) {
        new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
    }
}

结论:
构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的

2.以set方式注入依赖:

code:
ServiceA:

import org.springframework.stereotype.Component;
@Component
public class ServiceA {
    private ServiceB serviceB;
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
        System.out.println("A 里面设置了B");
    }
}```
ServiceB:

```java

import org.springframework.stereotype.Component;

@Component
public class ServiceB {

    private ServiceA serviceA;

    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
        System.out.println("B 里面设置了A");
    }
}

ClientSet:

public class ClientSet {
    public static void main(String[] args) {

        //创建serviceA
        ServiceA serviceA = new ServiceA();

        //创建serviceB
        ServiceB serviceB = new ServiceB();

        //将serviceA注入到serviceB中
        serviceB.setServiceA(serviceA);

        //将serviceB注入到serviceA中
        serviceA.setServiceB(serviceB);

    }
}
 

spring容器:
默认的单例(singleton)的场景是支持循环依赖的,不报错
原型(Prototype)的场景是不支持循环依赖的,报错

步骤:
1.
applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--
        1.spring容器默认的单例模式可以解决循环引用,单例默认支持
        2.spring容器原型依赖模式scope="prototype"多例模式下不能解决循环引用
    -->

    <!--depends-on 的意思就是当前这个bean如果要完成,先看depends-on指定的bean是否已经完成了初始化-->
    <!--scope="prototype"代表每次都要新建一次对象-->


    <bean id="a" class="com.hhf.study.spring.circulardepend.A" >
        <property name="b" ref="b"/>
    </bean>
    <bean id="b" class="com.hhf.study.spring.circulardepend.B">
        <property name="a" ref="a"/>
    </bean>
</beans>

默认单例,修改为原型scope=“prototype” 就导致了循环依赖错误

ClientSpringContainer:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
 * Error creating bean with name 'a': 578624778
 * Requested bean is currently in creation: Is there an unresolvable circular reference?
 *
 *
 * 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,因为单例的时候只有一份,随时复用,那么就放到缓存里面
 * 而多例的bean,每次从容器中荻取都是—个新的对象,都会重B新创建, 
 * 所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
 */
public class ClientSpringContainer {
    public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            A a = context.getBean("a",A.class);
            B b = context.getBean("b",B.class);
    }
}
 

循环依赖异常:
在这里插入图片描述
重要结论(spring内部通过3级缓存来解决循环依赖):

DefaultSingletonBeanRegistry:
在这里插入图片描述
第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂

在这里插入图片描述
所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map

循环依赖Debug:

实例化/初始化
实例化:堆内存中申请一块内存空间(租赁好房子,自己的家具东西还没有搬家进去)
初始化属性填充:完成属性的各种赋值(装修、家电家具进场)

3大Map和四大方法,总体相关对象:
在这里插入图片描述
三级缓存+四大方法:
1.getSingleton:希望从容器里面获得单例的bean,没有的话
2.doCreateBean: 没有就创建bean
3.populateBean: 创建完了以后,要填充属性
4.addSingleton: 填充完了以后,再添加到容器进行使用

第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

在这里插入图片描述
A/B两对象在三级缓存中的迁移说明:

  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B

  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
    然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A

  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
    然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
    在这里插入图片描述
    在这里插入图片描述
    Debug技巧:

(Step Over)f6代表是单步,一步步走

(Step into)f5代表是源码天生的自然进入,这是打debug本身JDK自带的源码,源码里面没有你自己所写的代码,

(Force Step Into)Alt+Shift+F7 这个是强制进入,这个一般用来debug强制进入自己所写的源代码

一般源码级别的调试, (Step into)f5足够了,但如果你要用(Force Step Into)Alt+Shift+F7 也完全可以。

如果找不到刚才停留的那一行的debug,如果断点打飞了怎么办,这个时候请选择Show Execution Point Alt+F10(也叫归位)

全部Debug断点:
在这里插入图片描述
总结spring是如何解决的循环依赖?
Spring创建bean主要分为两个步骤,
创建原始bean对象,接着去填充对象属性和初始化
每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个
当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,
不同的是:
这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建
既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

在这里插入图片描述
Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品。
实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring为了解决单例的循环依赖问题,使用了三级缓存
其中一级缓存为单例池〈 singletonObjects)
二级缓存为提前曝光对象( earlySingletonObjects)
三级缓存为提前曝光对象工厂( singletonFactories)。

假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程

spring解决循环依赖的整个流程图:

在这里插入图片描述

Debug的步骤---->Spring解决循环依赖过程:

1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
2 在getSingleton()方法中,从一级缓存中查找,没有,返回null
3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
6 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
7 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
9 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
10 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

也就是说当某个 bean 进入到 2 级缓存的时候,说明这个 bean 的早期对象被其他 bean 注入了,那么,这个 bean 还是半成品,还未完全创建好的时候,已经被别人拿去使用了,所以必须要有 3 级缓存,2 级缓存中存放的是早期的被别人使用的对象,如果没有 2 级缓存,是无法判断这个对象在创建的过程中,是否被别人拿去使用了。

为什么必须是三级缓存?
3 级缓存是为了解决一个非常重要的问题:早期被别人拿去使用的 bean 和最终成型的 bean 是否是一个 bean,如果不是同一个,则会产生异常。
三级缓存是为了判断循环依赖的时候,早期暴露出去已经被别人使用的 bean 和最终的 bean 是否是同一个 bean,如果不是同一个则弹出异常,如果早期的对象没有被其他 bean 使用,而后期被修改了,不会产生异常,如果没有三级缓存,是无法判断是否有循环依赖,且早期的 bean 被循环依赖中的 bean 使用了。。

spring 容器默认是不允许早期暴露给别人的 bean 和最终的 bean 不一致的,
但是这个配置可以修改,而修改之后存在很大的分享,所以不要去改,通过下面这个变量控制

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#allowRawInjectionDespiteWrapping
private boolean allowRawInjectionDespiteWrapping = false;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值