【Java面试真题】Spring是如何解决循环依赖问题?

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO TOP红人

Java知识图谱点击链接:体系化学习Java(Java面试专题)

💕💕 感兴趣的同学可以收藏关注下不然下次找不到哟💕💕

✊✊ 感觉对你有帮助的朋友,可以给博主一个三连,非常感谢 🙏🙏🙏

在这里插入图片描述

写在前面

🔔这个面试题是面到 Spring 必问的,要想很好的去回答还是要研究下源码的,而不是纯靠背八股文。接下我就剖析下如何解决的,面试也可以按照我介绍的去回答。结构化的表达会给面试官留下一个好印象,觉得你是一个逻辑清晰的开发。

1、什么是循环依赖

循环依赖指的是在软件开发中,两个或多个模块之间存在相互依赖的情况,形成了一个闭环。换句话说,一个模块依赖于另一个模块,而另一个模块又依赖于第一个模块,从而导致无法解决依赖关系。

而Spring循环依赖是指在使用Spring框架进行依赖注入时,两个或多个Bean之间存在相互依赖的情况,形成了一个循环的依赖关系。

当两个或多个Bean互相依赖时,Spring容器会尝试解析这些依赖关系并完成依赖注入。然而,如果存在循环依赖,Spring容器无法确定先实例化哪个Bean,因为它们相互依赖且互相等待对方被创建。
在这里插入图片描述

2、循环依赖会引起什么问题?

循环依赖可能引起以下问题:

  1. 🎄 无法完成依赖注入:循环依赖会导致依赖关系无法解析,因为每个Bean都依赖于另一个正在创建的Bean。这可能会导致Spring容器无法完成依赖注入,从而抛出异常。

  2. 🎄 死锁:循环依赖可能导致死锁情况。当两个或多个Bean相互等待对方被创建时,可能会出现死锁,阻塞应用程序的执行。

  3. 🎄 不稳定的行为:循环依赖可能导致不稳定的行为。由于循环依赖的存在,Bean的创建顺序变得不确定,可能会导致意外的结果或错误。

  4. 🎄 难以理解和维护:循环依赖会增加代码的复杂性,使代码难以理解和维护。当存在循环依赖时,代码的结构变得混乱,增加了调试和排查问题的难度。

3、看一个 Spring 循环依赖的例子

package com.pany.camp.circle;

import org.springframework.stereotype.Component;

/**
 *
 * @description:  测试类A
 * @copyright: @Copyright (c) 2022 
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0 
 * @createTime: 2023-07-10 13:01
 */
@Component
public class A {

    private B b;

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

package com.pany.camp.circle;

import org.springframework.stereotype.Component;

/**
 *
 * @description:  测试类B
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-10 13:01
 */
@Component
public class B {

    private A a;

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

package com.pany.camp.circle;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 *
 * @description:  入口类
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-10 13:01
 */
@SpringBootApplication
public class CircularDependencyExample implements CommandLineRunner {

    @Autowired
    private A a;

    public static void main(String[] args) {
        SpringApplication.run(CircularDependencyExample.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println(a);
    }
}

这是一个金典的循环依赖的代码,工作中可能会遇到,例如 service 层之间互相调用,就会引起循环依赖。Spring 也会将这个问题打印出了,我们看下面的日志:

在这里插入图片描述

4、如何解决循环依赖呢?

通过 @Lazy 注解进行懒加载

package com.pany.camp.circle;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

/**
 *
 * @description:  测试类A
 * @copyright: @Copyright (c) 2022 
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0 
 * @createTime: 2023-07-10 13:01
 */
@Component
public class A {

    private B b;

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

5、Spring 是如何解决循环依赖?

Spring解决循环依赖的原理是通过使用三级缓存和提前暴露未完成的Bean来实现的。

当Spring容器创建Bean时,会经过三个阶段的缓存:单例对象缓存、早期对象缓存和已完成Bean缓存。

  1. 🎄 单例对象缓存:在创建Bean之前,Spring会先检查单例对象缓存中是否存在该Bean的实例。如果存在,直接返回该实例,避免重复创建。

  2. 🎄 早期对象缓存:如果单例对象缓存中不存在该Bean的实例,Spring会将正在创建的Bean实例放入早期对象缓存中。这时,Spring会创建一个ObjectFactory,用于提供一个代理对象,以便在创建过程中解决循环依赖。

  3. 🎄 已完成Bean缓存:当Bean的创建过程完成后,Spring会将其放入已完成Bean缓存中。这样,其他依赖该Bean的Bean就可以从缓存中获取到已完成的实例。

具体的解决循环依赖的过程如下:

  1. 🎄 创建A对象:当需要创建A对象时,Spring会首先创建一个A的早期对象,并将其放入早期对象缓存中。

  2. 🎄 注入B对象:在创建A对象时,发现A依赖于B对象。此时,Spring会检查B对象是否在早期对象缓存中。如果在缓存中找到B的早期对象,Spring会将该早期对象注入到A对象中。

  3. 🎄 创建B对象:接下来,Spring会创建B对象。在创建过程中,发现B依赖于A对象。由于A对象已经在早期对象缓存中,Spring会将A的早期对象注入到B对象中。

  4. 🎄 完成A对象:当A对象的创建过程完成后,Spring会将其放入已完成Bean缓存中。

  5. 🎄 完成B对象:最后,当B对象的创建过程完成后,Spring会将其放入已完成Bean缓存中。

通过使用三级缓存和提前暴露未完成的Bean,Spring能够在循环依赖的情况下正确地解决依赖注入的问题。这样,即使存在循环依赖,Spring也能够确保依赖的Bean在正确的时间点被注入,避免了循环依赖导致的无限递归或空指针异常等问题。

6、面试还会补充问“二级缓存能解决循环依赖问题吗?”

答案是肯定,只不过代码上就要调整了。解决循环依赖,跟具体使用几级缓存其实没多大关系,主要还是在创建对象这个过程提前暴露,这个才是关键。

💕💕 本文由激流原创,原创不易,希望大家关注、点赞、收藏,给博主一点鼓励,感谢!!!
🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃
在这里插入图片描述

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

激流丶

感觉小弟写的不错,给点鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值