Spring中的循环依赖问题

前言

Spring中的循环依赖其实是一个非常简单的东西,在面试过程中会经常被问到,一般面试官会问Spring中循环依赖问题怎么解决,或者是A类一个属性指向B,B类中一个属性指向A这种问题再Spring中怎么解决。嘿嘿,这要是真的从怎么解决的角度入手那就真的掉坑里了。都是在问怎么解决,又没说让你解决,都说了是Spring中。那么当然问的是Spring是怎么解决的!我们只需要说明循环依赖时怎么产生的,以及Spring是怎么解决的,加分项,为什么需要三级缓存!搞定这些Spring循环依赖没什么问题!

什么是循环依赖?

首先Spring中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖。这两种依赖其实都是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环
在这里插入图片描述
差不多就是这个意思!上代码详细描述构造器循环依赖,属性循环依赖;

构造器依赖

@Service
public class A {
    
    

    @Autowired
    private B b;

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

@Service
public class B {
    
    

    @Autowired
    private A a;

    public B(A a){
    
    
        this.a = a;
    }
    public void f(){
    
    
        System.out.println("BBBBBBBBB中的f方法");
    }
}

@ComponentScan(value = "com.tao.spring")
public class TestMain {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestMain.class);
        applicationContext.getBean(A.class).f();
    }
}

运行结果

二月 21, 2021 10:13:18 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [G:\GitLab\spring5\target\classes\com\tao\spring\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [G:\GitLab\spring5\target\classes\com\tao\spring\B.class]: Unsatisfied dependency expressed through constructor parameter 0; 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?
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [G:\GitLab\spring5\target\classes\com\tao\spring\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [G:\GitLab\spring5\target\classes\com\tao\spring\B.class]: Unsatisfied dependency expressed through constructor parameter 0; 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?
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:769)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:218)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1325)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1171)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
	at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$8/362239120.getObject(Unknown Source)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:88)
	at com.tao.spring.TestMain.main(TestMain.java:19)

属性循环依赖

@Service
public class A {
    
    

    @Autowired
    private B b;

    public B getB() {
    
    
        return b;
    }

    public void setB(B b) {
    
    
        this.b = b;
    }


    public void f(){
    
    
        b.f();
    }
}
@Service
public class B {
    
    

    @Autowired
    private A a;

    public A getA() {
    
    
        return a;
    }

    public void setA(A a) {
    
    
        this.a = a;
    }
    public void f(){
    
    
        System.out.println("BBBBBBBBB中的f方法");
    }
}

@ComponentScan(value = "com.tao.spring")
public class TestMain {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestMain.class);
        applicationContext.getBean(A.class).f();
    }
}

运行结果
在这里插入图片描述
Spring在处理构造循环依赖和属性依赖时存在两种不同的运行结果,这是因为Spring对于构造循环依赖问题是无法解决的,只能抛出BeanCurrentlyInCreationException这个异常,在构造循环运行报错结果中有显示这个异常,Spring在处理属性循环依赖是可以处理的,因为Spring是采用提前暴露对象的方法也就是俗称的三级缓存

Spring是怎么解决循环依赖的?

在上面已经给出答案了啊!也就是三级缓存!那么在将三级缓存具体是怎么解决属性循环依赖问题之前,我们先做一下知识储备!

前提知识:
我们知道在Spring创建对象时,先实例化–>填充属性–>初始化
在这里插入图片描述
有了Spring创建对象的流程后,我们就可以对号入座两种循环依赖,第一种构造循环依赖就是发生在实例化阶段,因为这里需要调用构造方法,第二种属性循环依赖就是发生在属性填充,因为这里是完成属性赋值操作!

三级缓存
这三级缓存分别指DefaultSingletonBeanRegistry类中的singletonObjects,earlySingletonObjects,singletonFactories
在这里插入图片描述

  1. singletonObjects:单例对象的cache
  2. earlySingletonObjects :提前暴光的单例对象的Cache
  3. singletonFactories : 单例对象工厂的cache

在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory。这里就是解决循环依赖的关键,发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用

Spring循环依赖源码分析

为什么需要三级缓存

如果只有一级缓存能不能解决循环依赖
首先我们搞清楚这里存放的对象是半成品、成品,半成品(只完成实例化,并没有完成初始化),成品(即完成实例化又完成初始化),如果成品和半成品都放到一级缓存中去了,(一级缓存是用来方便获取对象的)那么在多线程的环境下,系统都从一级缓存中获取数据,有可能获取到的是半成品,属性都会是null

如果只有二级缓存能不能解决循环依赖
那么有了二级缓存后,那么一级缓存就可以存储成品,二级缓存就可以存储半成品,可以区分开来,这样的情况二级缓存是够用的,二级缓存是可以解决循环依赖问题的!

三级缓存的意义
如果创建的是一个普通类,二级缓存是完全能搞定的,但是如果创建的类是一个代理类,那么就会有问题,

猜你喜欢

转载自blog.csdn.net/CSDN877425287/article/details/113924766
今日推荐