0
点赞
收藏
分享

微信扫一扫

Spring的循环依赖问题

GG_lyf 2022-04-26 阅读 117

一、Spring的执行流程

在了解spring的循环依赖前,首先应该知道Spring的基本脉络:
在这里插入图片描述

二、循环依赖问题

1.循环依赖问题产生的原因

如下图所示(简单来说就是A类的构造函数需要B对象,而B类的构造函数需要A对象)
在这里插入图片描述

2.出现循环依赖的案例:

比方说有A和B两个类,A依赖B,B也依赖于A:

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

public class B {
    private A a;
    public B(A a){
        this.a = a;
    }
}
    <bean name="a" class="beans.A">
        <constructor-arg ref="b"></constructor-arg>
    </bean>
    <bean name="b" class="beans.B">
        <constructor-arg ref="a"></constructor-arg>
    </bean>

当执行new ClassPathXmlApplicationContext(application.xml)时,会产生下图所示的报错信息:
在这里插入图片描述

三、Spring解决循环依赖的办法

方法1.使用默认的singleton单例模式+set注入+三级缓存

//下面只展示A类,B类也会做出类似的改变
public class A {
    private B b;
	//A有默认的无参构造方法!
    public void setB(B b) {
        this.b = b;
    }
}
    <bean name="a" class="beans.A">
        <property name="b" ref="b"></property>
    </bean>
    <bean name="b" class="beans.B">
        <property name="a" ref="a"></property>
    </bean>

经过上面的改造,循环依赖就会解决,下面的是对源码的剖析过程:

Spring源码中三级缓存如下图所示:
请添加图片描述
在Spring的refresh(finishBeanFactoryInitialization)方法中,会完成IOC容器中所有单例对象的创建。在doCreateBean方法中首先会通过createBeanInstance方法完成a对象的实例化操作(反射clazz.getConstructor().newInstance()),完成实例化操作后,会将该实例化对象形成lambda表达式存放于三级缓存中,在populateBean方法里,会接着对a对象进行相关属性赋值操作,其中就包含了引用类型b的赋值操作,由于单例池中还没有B类型的对象,于是spring就会接着创建b对象,完成b的实例化操作,也会将b的实例化对象放在三级缓存中,在对b对象进行populateBean属性赋值时,会将存放于三级缓存中的a对象取出并放在二级缓存里(此时a对象的b属性还没有赋值,a此时并不是一个完整的对象),在b对象完成了所有的属性填充后,执行invokeAwareMethods、BeanPostProcessor、init-method后,会将b对象放在一级缓存中并将三级缓存里的b对象移除。
当b对象创建完成后,a对象即可继续populateBean,从一级缓存里把b对象取出完成b属性的填充。于是循环依赖问题解决!!!!!!

bean的初始化流程细节如下,红色代表的是最后销毁的对象:
在这里插入图片描述

2.比较特殊的循环依赖问题

//xml中的扫描器
<context:component-scan base-package="beans"></context:component-scan>
//配置类中的扫描器
@ComponentScan
----------------------------------------------------------------------
@Component
public class A {
    @Autowired
    private B b;
  	public A(B b){
       this.b = b;
   	}
}
@Component
public class B {
    @Autowired
    private A a;
   	public B(A a){
       this.a = a;
   	}
    
}

以上的代码会出现循环依赖问题,解决办法有两种:

方法1:删除上面的有参构造方法 或者 新增一个无参构造方法(本质都是让A和B具有无参构造),如下方所示:
@Component
public class A {
    @Autowired
    private B b;
}
@Component
public class B {
    @Autowired
    private A a;
}

在方法1的基础上有个特殊的案例:当某个类的方法中标注了@Async的注解,会产生报错问题!

//xml中的配置支持@Async
 <task:annotation-driven executor="annotationExecutor" />
 <!-- 支持 @Async 注解 -->
 <task:executor id="annotationExecutor" pool-size="20"/>
//配置类中配置支持@Async
@EnableAsync
----------------------------------------------------------------------------------
//仅仅是给A类添加了一个@Async标注的方法,就会报错
@Component
public class A {
    @Autowired
    private B b;
    @Async
    public void test(){
        System.out.println(b);
    }
}

在这里插入图片描述
其主要原因是因为a对象会被AsyncAnnotationBeanPostProcessor后置处理器替换成一个新的代理对象,巴拉巴拉导致的(留个记号,博主以后补充!!
解决办法就是在b属性上加上@Lazy注解,如下发:

@Component
public class A {
    @Autowired
    @Lazy
    private B b;

    @Async
    public void test(){
        System.out.println(b);
    }
}
方法2:在上面的有参构造上面添加@Lazy注解也能解决循环依赖问题,如下图所示:
//B类中也需要做出相应的改变!
@Component
public class A {
    @Autowired
    private B b;

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

加上@Lazy注解可以保证使用b对象时才会给A类注入b属性。

举报

相关推荐

0 条评论