spring5源码系列--循环依赖的设计思想

简介: spring5源码系列--循环依赖的设计思想

一. 循环依赖的三级缓存设计



再循环依赖的过程中设计了三级缓存, 他们的作用分别是

1. 一级缓存: 用来存放完整的bean

2. 二级缓存: 用来存放早期的,纯净的bean

3. 三级缓存: 用来存放接口函数.


/** Cache of singleton objects: bean name to bean instance. */
    /**
     * 一级缓存  这个就是我们大名鼎鼎的缓存池, 用于保存我们所有的实例bean
     */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    /** Cache of singleton factories: bean name to ObjectFactory. */
    /**
     * 三级缓存  该map用户缓存key为beanName, value为objectFactory(包装为早期对象)
     */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    /** Cache of early singleton objects: bean name to bean instance. */
    /**
     * 二级缓存, 用户缓存我们的key为beanName, value是我们的早期对象(此时对象属性还没有...)
     */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);


细细想来, 这三个缓存都非常有存在的必要.


1.1 引入一级缓存


刚开始, 只有一级缓存, 在整个bean创建完成以后, 将其完整的bean放入到一级缓存中. 这样有什么问题?


1. bean创建一共有三大步骤, (实例化, 属性赋值, 初始化) 等到整个过程都创建完, 在存入一级缓存, 多线程怎么办? 第一个线程创建bean的过程中, 又来了一个线程, 他发现一级缓存这时候还没有, 就回去再次创建. 那不就重复了么? ioc要求, bean是单例的.

2. 加锁, 加锁能否解决这个问题? 能, 但是效率超级低. 对一级缓存加锁, 那么所有的对象创建过程中都要等待. 哪怕人家已经创建成功过. 效率太低, 不能接受

3. 于是就引入了二级缓存.


1.2 引入二级缓存


二级缓存的引入, 可以解决一级缓存创建bean链路过长的问题,他在bean一旦被创建,立刻就放入到二级缓存. 整个bean创建完成以后, 在放入到一级缓存,删除二级缓存. 这样做可以解决多线程创建bean的问题. 缩短了整个链路. 同时, 每次从缓存中先获取bean, 如果一级缓存中已经有了,那么直接返回. 不用在执行后面的创建代码

那么,二级缓存有什么问题呢?


这就还需要知道一个问题, 那就是动态代理创建bean. 什么时候, 去使用动态代理创建bean? 通常我们说在初始化之后, 调用bean的后置处理器创建bean. 这只是大多数bean创建动态代理的时候. 那如果有循环依赖呢? 有循环依赖, 还在初始化之后创建就晚了. 这是需要在实例化之后创建. 这样,动态代理的代码就和创建bean耦合在一块了. 违背单一性原则.

于是, 引入了三级缓存


1.3 引入三级缓存


三级缓存的引入是为了解决耦合问题. 让每一个方法只做一件事. 巧妙的使用了接口函数.

这个接口函数什么用呢? 就相当于js中的回调函数. 我在前面定义好, 但是不执行. 直到满足条件了, 才执行. 这个方法, 可以大范围应用到实践工作中.

比如: 调用动态代理创建bean. 刚开始实例化完成以后, 我就赋予你这个能力, 你可以调用动态代理. 但是, 到后面, 你是否真的能够运用这个能力呢? 不一定, 只有满足条件, 才会运用这个能力.


二. 定义接口函数, 也叫钩子函数



在循环依赖源码中, 两次使用到接口函数的方式.

第一个是创建bean的时候. 第二个是三级缓存

下面来看看源码,


第一次: 创建bean的时候, 定义了一个钩子函数createBean()

sharedInstance = getSingleton(beanName, () -> {
    try {
        // 这里定义了一个钩子函数. 此时只是定义, 并不执行. 在真正需要创建bean的地方才会执行
        return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
        // Explicitly remove instance from singleton cache: It might have been put there
        // eagerly by the creation process, to allow for circular reference resolution.
        // Also remove any beans that received a temporary reference to the bean.
        destroySingleton(beanName);
        throw ex;
    }
});

实际上调用的时机是: 在getSingleton方法里面. 回调接口函数.


public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            // 第一步: 从一级缓存中获取单例对象
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                // 第二步: 将bean添加到singletonsCurrentlyInCreation中, 表示bean正在创建
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    // 第三步: 这里调用getObject()钩子方法, 就会回调匿名函数, 调用singletonFactory的createBean()
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    // Has the singleton object implicitly appeared in the meantime ->
                    // if yes, proceed with it since the exception indicates that state.
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

第二次调用: 是在三级缓存定义的时候


调用addSingletonFactory(...)定义了一个钩子函数. 这里仅仅是定义, 并不执行


// 把我们的早期对象包装成一个singletonFactory对象, 该对象提供了getObject()方法, 把静态的bean放到三级缓存中去了.
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

然后进入到addSingletonFactory内部, 只是把singletonFactory放入到了三级缓存中, 这里只是定义, 也并没有执行


protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                // 加入到三级缓存中, 暴露早期对象用于解决循环依赖.
                this.singletonFactories.put(beanName, singletonFactory);
                // 从二级缓存中删除
                this.earlySingletonObjects.remove(beanName);
                // 添加到已经注册的singleton实例.
                this.registeredSingletons.add(beanName);
            }
        }
    }

什么时候执行的呢? 再从缓存中获取对象的时候.

@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从一级缓存中获取bean实例对象
        Object singletonObject = this.singletonObjects.get(beanName);
        /**
         * 如果在第一级的缓存中没有获取到对象, 并且singletonsCurrentlyIncreation为true,也就是这个类正在创建.
         * 标明当前是一个循环依赖.
         *
         * 这里有处理循环依赖的问题.-- 我们使用三级缓存解决循环依赖
         */
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                /**
                 * 从二级缓存中拿bean, 二级缓存中的对象是一个早期对象
                 * 什么是早期对象?就是bean刚刚调用了构造方法, 还没有给bean的属性进行赋值, 和初始化, 这就是早期对象
                  */
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    /**
                     * 从三级缓存拿bean, singletonFactories是用来解决循环依赖的关键所在.
                     * 在ios后期的过程中, 当bean调用了构造方法的时候, 把早期对象包装成一个ObjectFactory对象,暴露在三级缓存中
                      */
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        /**
                         * 在这里通过暴露的ObjectFactory包装对象. 通过调用他的getObject()方法来获取对象
                         * 在这个环节中会调用getEarlyBeanReference()来进行后置处理
                         */
                        singletonObject = singletonFactory.getObject();
                        // 把早期对象放置在二级缓存中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 删除三级缓存
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }


相关文章
|
2天前
|
人工智能 Java Spring
Spring Boot循环依赖的症状和解决方案
Spring Boot循环依赖的症状和解决方案
|
2天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
37 0
|
2天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
2天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
20 6
|
2天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
29 3
|
2天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
16 1
|
2天前
|
缓存 Java 开发工具
【spring】如何解决循环依赖
【spring】如何解决循环依赖
14 0
|
2天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
103 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
2天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
2天前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
24 0