【Spring】循环依赖

目录标题

https://docs.spring.io/spring-framework/reference/

什么是循环依赖

多个Bean互相引用,形成环路

循环依赖场景

  • 原型Bean的循环依赖
  • 单例bean之构造注入的循环依赖
  • 单例bean之setter注入的循环依赖

前两者无法解决,最后一种可以通过Spring提供的三级缓存来进行实现。

Java SE 演示

java 复制代码
@Component
public class ServiceA {

    private ServiceB serviceB;

    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
        System.out.println("A 里面设置了B");
    }

    // public ServiceA(ServiceB serviceB) {
    //     this.serviceB = serviceB;
    // }
}
java 复制代码
@Component
public class ServiceB {

    private ServiceA serviceA;

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

    // public ServiceB(ServiceA serviceA) {
    //     this.serviceA = serviceA;
    // }
}
java 复制代码
public class ClientDemo {
    public static void main(String[] args) {
        clientSet();
        // clientConstruct();
    }

    /**
     * setter注入
     */
    private static void clientSet() {
        //创建serviceA
        ServiceA serviceA = new ServiceA();

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

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

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

    /**
     * 构造注入
     */
    private static void clientConstruct(){
        // new ServiceA(new ServiceB(new ServiceA(new ServiceB())));
    }
}

Spring 容器演示

xml 复制代码
<!--    <bean id="a" class="com.example.demo.circulardependency.spring.A" scope="singleton">-->
    <bean id="a" class="com.example.demo.circulardependency.spring.A" scope="prototype">
        <property name="b" ref="b"/>
    </bean>

<!--    <bean id="b" class="com.example.demo.circulardependency.spring.B" scope="singleton">-->
    <bean id="b" class="com.example.demo.circulardependency.spring.B" scope="prototype">
        <property name="a" ref="a"/>
    </bean>
java 复制代码
public class ClientSpringContainer {
    public static void main(String[] args) {
        sampleDemo();
    }

    /**
     * spring
     *
     * 2024/2/2 11:40
     */
    private static void sampleDemo() {
        /**
         * setter注入
         * 
         * 11:39:14.055 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
         * ---A created success
         * 11:39:14.064 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
         * ---B created success
         */
        /**
         * 构造注入
         * 
         * Exception in thread "main" org.springframework.beans.factory.BeanCreationException:
         *
         */
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        A a = context.getBean("a", A.class);
        B b = context.getBean("b", B.class);
    }
}

三级缓存

Spring循环依赖 - CSDN博客

核心知识

三级缓存

一级缓存 :Map<String, Object> singletonObjects,我愿称之为成品单例池,常说的 Spring 容器就是指它,我们获取单例 bean 就是在这里面获取的,存放已经经历了完整生命周期的Bean对象
二级缓存 :Map<String, Object> earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整,可以认为是 半成品的 bean, 实例化但未初始化的Bean对象
三级缓存 :Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂(FactoryBean),用于生产(创建)对象

java 复制代码
/** Cache of singleton objects: bean name to bean instance. */
// 一级缓存:singleton对象的缓存:bean名称 - bean实例。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
// 三级缓存:单例工厂的缓存:bean名称 - ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
// 二级缓存:早期singleton对象的缓存:bean名称 - bean实例。
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

四大方法

  1. getSingleton():从容器里面获得单例的bean,没有的话则会创建 bean
  2. doCreateBean():执行创建 bean 的操作(在 Spring 中以 do 开头的方法都是干实事的方法)
  3. populateBean():创建完 bean 之后,对 bean 的属性进行填充
  4. addSingleton():bean 初始化完成之后,添加到单例容器池中,下次执行 getSingleton() 方法时就能获取到

三级缓存中的迁移

  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

三级缓存源码分析

【Spring】三级缓存

源码思维导图

Spring三级缓存源代码执行图

源码图例

课前问题

还剩下三个:

  • 为什么不可以用二级缓存?这部分我在网上搜寻了一下,跟AOP的代理有关(由于目前我对AOP不熟,怕误导了大家,就先欠着)
  • 开发中解决循环依赖?欠着
  • 循环依赖遇上AOP?欠着



推荐阅读

相关推荐
不爱编程的小九九10 分钟前
小九源码-springboot097-java付费自习室管理系统
java·开发语言·spring boot
独自破碎E32 分钟前
LeetCode 381: O(1) 时间插入、删除和获取随机元素 - 允许重复
java·算法·leetcode
程语有云34 分钟前
生产事故-Caffeine缓存误用之临下班的救赎
java·缓存·caffeine·阻塞·log·生产事故
Miraitowa_cheems1 小时前
LeetCode算法日记 - Day 81: 最大子数组和
java·数据结构·算法·leetcode·决策树·职场和发展·深度优先
CodeCraft Studio1 小时前
国产化Word处理控件Spire.Doc教程:用Java实现TXT文本与Word互转的完整教程
java·c#·word·spire.doc·word文档转换·txt转word·word转txt
徐子童1 小时前
数据结构---优先级队列(堆)
java·数据结构·面试题·优先级队列··topk问题
滑水滑成滑头1 小时前
**标题:发散创新:智能交通系统的深度探究与实现**摘要:本文将详细
java·人工智能·python
siriuuus1 小时前
Maven 核心概念及生命周期
java·maven
闭着眼睛学算法2 小时前
【双机位A卷】华为OD笔试之【哈希表】双机位A-跳房子I【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
java·c语言·c++·python·算法·华为od·散列表
孔明兴汉2 小时前
Cursor MCP 第一章-第一节-MCP协议简介.md
java·ai