Sping源码:三级缓存

news2024/10/1 8:34:06

目录

  • 一、概念
    • 1、三级缓存的作用
    • 2、循环依赖的含义
  • 二、代码
    • 1、代码下载
    • 2、文件功能介绍
    • 3、源码分析
      • 3.1、找到获取A对象的位置,打断点进行debug操作
      • 3.2、一步步找到在A对象中注入B对象的位置
      • 3.3、一步步找到B对象注入A对象的位置
      • 3.4、往下找到通过三级缓存解决循环依赖的地方
      • 3.5、完成对象B注入对象A
      • 3.6、完成对象B的剩余流程,进而回到对象A注入对象B的地方
      • 3.7、查看对象A不会再次执行代理代码的地方
      • 3.8、找到往一级缓存中添加对象A的代理对象的位置
      • 3.9、往对象C中注入对象A
  • 三、流程分析
  • 四、参考文章

一、概念

1、三级缓存的作用

  • 解决循环依赖(即:三级缓存 singletonFactories的功能)
  • 实现动态代理 (即:二级缓存 earlySingletonObjects的功能)
  • 存储最终对象 (即:一级缓存 singletonObjects的功能)

2、循环依赖的含义

N个类循环(嵌套)引用。

通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):

在这里插入图片描述

注意:其实可以N=1,也就是极限情况的循环依赖:自己依赖自己

另需注意:这里指的循环引用不是方法之间的循环调用,而是对象的相互依赖关系。(方法之间循环调用若有出口也是能够正常work的)

可以设想一下这个场景:如果在日常开发中我们用new对象的方式,若构造函数之间发生这种循环依赖的话,程序会在运行时一直循环调用最终导致内存溢出,示例代码如下:

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println(new A());
    }

}

class A {
    public A() {
        new B();
    }
}

class B {
    public B() {
        new A();
    }
}

这是一个典型的循环依赖问题。Spring通过三级缓存机制巧妙的解决循环依赖问题~

二、代码

1、代码下载

百度网盘链接:https://pan.baidu.com/s/1Cq1kTJJesOfl0eHIU3cMIA?pwd=gyg1

提取码:gyg1

2、文件功能介绍

  • CodeStudyApplication类:主启动类
  • A类:通过IOC方式注入类B和类C的单例对象,用来测试循环依赖;并且在成员方法test()上添加@Transactional注解,用来测试动态代理
  • B类:通过IOC方式注入类A的单例对象,用来测试循环依赖
  • C类:通过IOC方式注入类A的单例对象,用来测试循环依赖,类C和类B的主要区别是“当B对象触发A对象的循环依赖后,可以获取A对象的代理对象,然后看C对象如何获取A对象的代理对象”
  • application.yml:添加一些mysql信息,大家不用修改,不会报错

3、源码分析

注意: 博主使用的开发工具是IDEA,所以提到的快捷键都是IDEA中的

3.1、找到获取A对象的位置,打断点进行debug操作

当程序启动时,最先运行的是com.atguigu.test.CodeStudyApplication类中的main方法。
根据代码执行顺序,我们按着Ctrl按键后点击SpringApplication类的静态方法run()

在这里插入图片描述

现在进入了SpringApplication类的静态方法run(),然后我们按着Ctrl按键后点击重载的静态方法run()

在这里插入图片描述

继续按着Ctrl按键后点击成员方法run()

在这里插入图片描述

现在进入了一个方法内容较多的run()方法,继续按着Ctrl按键后点击成员方法refreshContext(context);,由于方法体内很多内容并非本节重点,所以不在本节讲解,其中IOC的主要内容都在refreshContext(context)方法体现。

在这里插入图片描述

继续按着Ctrl按键后点击成员方法refresh(context);

在这里插入图片描述

继续按着Ctrl + Alt按键后点击applicationContext对象的refresh()方法

下图中refresh方法参数中的applicationContext对象大家应该很熟悉,来看一道常见面试题:“BeanFactory与ApplicationContext的区别?”,哈哈哈,想起来了吧~

在这里插入图片描述

上述操作之后,页面中会出现三个实现类供我们选择,我们选择实现类ServletWebServerApplicationContext,该实现类是我们常用的,至于选它的原因不是本节重点,所以不再解释。

在这里插入图片描述

继续按着Ctrl按键后点击父类方法super.refresh();

在这里插入图片描述

继续按着Ctrl按键后点击成员方法finishBeanFactoryInitialization(beanFactory);

其中对象构建、注入其他对象、代理、初始化等流程都在该方法中,其他代码不是本节重点,所以不在讲解。

在这里插入图片描述

继续按着Ctrl + Alt按键后点击beanFactory.preInstantiateSingletons();方法:

在这里插入图片描述

我们把断点打在成员方法getBean(beanName);

其中遍历的beanNames方法来自于成员集合变量beanDefinitionNames,该集合的值来自于@ComponentScan扫描,以及其他注解操作的结果

在这里插入图片描述

3.2、一步步找到在A对象中注入B对象的位置

通过按F9快速运行程序遍历beanNames,找到beanName等于a的情况停下:

在这里插入图片描述

点击F7进入方法getBean(beanName);

在这里插入图片描述
点击F7进入方法doGetBean()方法:

在这里插入图片描述

点击F8进入下一步,到达getSingleton(beanName);这一行:

在这里插入图片描述

点击F7进入以下方法:

在这里插入图片描述

再次点击F7进入以下方法:

在这里插入图片描述

此时singletonObjects中不存在名字叫做a的对象,所以singletonObject等于null;由于A对象还没有开始创建,所以isSingletonCurrentlyInCreation(beanName)的结果是false,因此第1个if方法不会执行,所以最终得知singletonObject的结果是null

在这里插入图片描述

点击两次F8就可以回到Object sharedInstance = getSingleton(beanName);

在这里插入图片描述

依然在上述doGetBean方法中,我们往下找到if (mbd.isSingleton())这一行,然后在这一行上单击鼠标右键,点击Run toCursor

在这里插入图片描述

然后点击F8往下执行一行代码,此时就到了getSingleton方法处,该方法有2个参数,分别是bean对象名称和一个匿名内部类,此时点击F7进入getSingleton方法;

我们首先看下beforeSingletonCreation(beanName);,进入该方法的方法体看下,其中singletonsCurrentlyInCreation集合记录了正在创建的对象,对象A不在里面,所以会被加入到singletonsCurrentlyInCreation集合中

在这里插入图片描述

然后往下可以看到singletonObject = singletonFactory.getObject();这行代码,真正执行的是形参对应的匿名内部类中的实现方法,回顾上一张截图,执行getObject方法其实就是执行createBean方法:

在这里插入图片描述

回顾一下上面那张截图,我们找到createBean方法:

在这里插入图片描述

点击Ctrl + Alt,在createBean方法上点击鼠标左键,就可以进入该方法:

在这里插入图片描述

继续按着Ctrl按键后点击成员方法doCreateBean(beanName, mbdToUse, args);

在这里插入图片描述

在该方法中,往下滚动屏幕,可以看到addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));,先来分析一下addSingletonFactory方法的方法体;现在回头看下addSingletonFactory方法的第2个参数,这是一个匿名内部类,我们后面会用到它,大家用笔记一下,后续我们还会回来的~

在这里插入图片描述

然后往下滑可以看到populateBean(beanName, mbd, instanceWrapper);方法:

在这里插入图片描述

继续按着Ctrl + Alt按键后点击成员方法postProcessProperties()方法:

在这里插入图片描述

在A类中注入对象B使用@Resource注解,所以我们选择CommonAnnotationBeanPostProcessor

在这里插入图片描述

继续按着Ctrl按键后点击成员方法metadata.inject(bean, beanName, pvs);

在这里插入图片描述

继续按着Ctrl按键后点击成员方法element.inject(target, beanName, pvs);

在这里插入图片描述

继续按着Ctrl + Alt按键后点击成员方法getResourceToInject()方法,选择CommonAnnotationBeanPostProcessor类:

在这里插入图片描述

上述方法的作用就是在A对象中注入B对象

3.3、一步步找到B对象注入A对象的位置

由于在A类中引入的对象B上没有加@Lazy注解,所以lazyLookupfalse,因此走getResource方法,继续按着Ctrl按键后点击成员方法getResource(this, requestingBeanName)

在这里插入图片描述

继续按着Ctrl按键后点击成员方法autowireResource()

在这里插入图片描述

继续按着Ctrl + Alt按键后点击方法resolveBeanByName()

在这里插入图片描述

可以看到代码中调用了getBean方法,非常熟悉吧,又回到最初的起点,继续根据name信息获取Bean工厂中的对象,现在是从对象A中准备注入对象B

在这里插入图片描述

后面过程和通过对象A的名称来调用getBean(name)方法一样,我们把过程走一下吧~

  • 点击Ctrl:org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class<T>)
  • 点击Ctrl:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
  • 点击Ctrl:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
  • 点击Ctrl:org.springframework.beans.factory.support.AbstractBeanFactory#createBean
  • 点击Ctrl:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
  • 点击Ctrl:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
  • 点击Ctrl + Alt:org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessProperties,选择CommonAnnotationBeanPostProcessor
  • 点击Ctrl:org.springframework.beans.factory.annotation.InjectionMetadata#inject
  • 点击Ctrl:org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
  • 我们在对象B中注入了对象A,此时执行的field.set(target, getResourceToInject(target, requestingBeanName));就是获取对象A的
  • 点击Ctrl + Alt:org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#getResourceToInject,选择CommonAnnotationBeanPostProcessor
  • 由于B类中注入的对象A也没加@Lazy注解,所以走getResource(this, requestingBeanName)方法
  • 点击Ctrl:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResource
  • 点击Ctrl:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
  • 点击Ctrl + Alt:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
  • 现在我们来到getBean方法,终极反转了,现在是从对象B中准备注入对象A
    在这里插入图片描述

3.4、往下找到通过三级缓存解决循环依赖的地方

现在是B对象准备注入对象A,继续按着Ctrl按键后点击方法getBean(name, descriptor.getDependencyType())

在这里插入图片描述

继续按着Ctrl按键后点击方法doGetBean(name, requiredType, null, false)

在这里插入图片描述

继续按着Ctrl按键后点击方法getSingleton(beanName)

在这里插入图片描述

继续按着Ctrl按键后点击方法getSingleton(beanName, true)

在这里插入图片描述

然后进入方法体中,由于对象A还没有创建完成,所以一级缓存singletonObjects肯定没有,因此singletonObject对象是null

在这里插入图片描述

我们看下isSingletonCurrentlyInCreation(beanName)方法的方法体,其中singletonsCurrentlyInCreation集合作用在上面已经解释过了,就是记录当前正在被创建的bean对象名称,在处理A对象时调用getSingleton》beforeSingletonCreation方法中已经将a存储到了singletonsCurrentlyInCreation集合中,所以此处isSingletonCurrentlyInCreation(beanName)方法的结果肯定是true

在这里插入图片描述

此时代码继续往下走,由于此时对象A的信息存储在三级缓存singletonFactories中,因此也无法从earlySingletonObjects集合中去除对象A的相关信息,所以singletonObject = this.earlySingletonObjects.get(beanName);的结果也是null

在这里插入图片描述

由于上面调用getSingleton()方法时传入的参数allowEarlyReferencetrue,因此方法会继续向下走

在这里插入图片描述

为解决并发创建对象问题,所以需要将一级对象singletonObjects锁起来,在同步代码块中尝试从一级缓存singletonObjects和二级缓存earlySingletonObjects中得到对象A,但是此时对象A存储在三级缓存singletonFactories中,因此依然获取不到

在这里插入图片描述

此时准备从三级缓存singletonFactories中获取对象A,由于之前在往对象A进行依赖注入之前往三级缓存中填充过值,所以此时singletonFactory是有值的,来给大家回忆一下填充值的位置

在这里插入图片描述

此时singletonFactory的值就是上图框中的匿名内部类,所属的接口是一个函数式接口,此时代码继续往下执行:

在这里插入图片描述

当执行singletonFactory.getObject()的时候,其实执行的是匿名内部类中的方法,我们把断点打到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法中的if判断上

在这里插入图片描述继续按着Ctrl + Alt按键后点击方法bp.getEarlyBeanReference(exposedObject, beanName)

在这里插入图片描述

此时将没有被包装过的对象放到earlyProxyReferences集合中,该集合作用是记录已经执行过代理方法的对象信息,即执行过wrapIfNecessary(bean, beanName, cacheKey)方法的对象信息;由于代理这个动作只能执行一次,下次不能在执行了,所以将已经执行过代理的对象信息记录在earlyProxyReferences集合中,后续在需要执行代理的时候,我们判断下该集合中是否已经包含,进而决定是否还要执行wrapIfNecessary方法,这个我们后续会聊到的,这里不在展开~

在这里插入图片描述

大家注意现在情况是往对象B中注入对象A,目前处于一个获取对象A的过程中,由于A类的test()方法上添加了@Transactional注解,所以在执行wrapIfNecessary方法之后将会生成A类的代理对象,既然到这里了,我们跟进去看下

继续按着Ctrl按键后点击方法wrapIfNecessary(bean, beanName, cacheKey)

在这里插入图片描述
getAdvicesAndAdvisorsForBean方法执行之后,就可以知道代理信息,如下:

在这里插入图片描述
继续按着Ctrl按键后点击方法createProxy()

在这里插入图片描述

进入该方法往下就可以找到真正执行代理的地方,即:proxyFactory.getProxy(classLoader),继续按着Ctrl按键后点击方法proxyFactory.getProxy(classLoader)

在这里插入图片描述

继续按着Ctrl按键后点击方法createAopProxy()

在这里插入图片描述
继续按着Ctrl + Alt按键后点击方法createAopProxy(this)

在这里插入图片描述
可以非常清晰的看到使用CGLib还是JDK动态代理方式
在这里插入图片描述
最终一层层返回代理结果,那么getEarlyBeanReference()方法的返回值就是A类的代理对象了,可以非常明显的看到代理对象是CGLib类型的

在这里插入图片描述

我们此时依然在B对象中注入A对象的过程中,目前准备获取对象A或者A类的代理对象,上面方法是在执行singletonFactory.getObject()方法,也就是那个匿名内部类中的方法,得到的结果singletonObject是一个A类的CGLib代理对象,此时将代理对象装在二级缓存earlySingletonObjects

在这里插入图片描述
此时就可以完成B类中的对象A注入了

3.5、完成对象B注入对象A

上面已经获取到对象A的代理对象了,我们一部分返回到注入对象A的地方:

  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
  • org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
  • org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResource
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
  • org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
    在这里插入图片描述

继续往上,回到对象B完成注入对象A之后,执行初始化代码的地方:

  • org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
  • org.springframework.beans.factory.annotation.InjectionMetadata#inject
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
    在这里插入图片描述

3.6、完成对象B的剩余流程,进而回到对象A注入对象B的地方

初始化流程就是执行一些初始化方法的过程,由于对象B的初始化流程不是本节重点,所以不在讲解,直接略过

在这里插入图片描述

  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
  • org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
  • org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
  • org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResource
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
  • org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject

现在回到l对象A注入对象B的地方

在这里插入图片描述
然后一步步回到对象A的初始化方法代码处:

  • org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
  • org.springframework.beans.factory.annotation.InjectionMetadata#inject
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
    在这里插入图片描述

3.7、查看对象A不会再次执行代理代码的地方

从上图可见,我们进入initializeBean方法,主要看方法体中基本快结尾的地方,这个里面隐藏着代理类的生成逻辑:

在这里插入图片描述

按着Ctrl点击进去:

在这里插入图片描述

我们一次又一次的debug进去,终于发现了生成代理对象的地方,大家熟悉earlyProxyReferences吗?

在B对象中注入对象A时,已经将对象A的信息塞在earlyProxyReferences集合中了,具体作用是说明对象A的代理对象已经生成并放在二级缓存earlySingletonObjects中了,所以if判断结果是false,因此wrapIfNecessary(bean, beanName, cacheKey)不会在执行了,这样可以避免该方法执行2次,并且可以避免无法再bean工厂中生成一致的bean对象

如果没有发生循环依赖,那么org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference方法不会执行,因此earlyProxyReferences集合中就不存在键为a的情况了,如果B类中没有注入对象A就可以达到。但是A类和B类产生了循环依赖,所以earlyProxyReferences中存储键为a的情况了

在这里插入图片描述

3.8、找到往一级缓存中添加对象A的代理对象的位置

对象A的实例化已经完成,代码继续往下执行,可以看到:

在这里插入图片描述

从二级缓存中获得对象A的代理对象:

在这里插入图片描述

然后将结果一路返回:

  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
    当匿名内部类中的方法执行完毕后,将回到singletonObject = singletonFactory.getObject();执行完成的状态
    在这里插入图片描述

往下继续看方法addSingleton()方法,目前singleonObject是一个:

在这里插入图片描述

往一级缓存中放入对象A的代理对象信息
在这里插入图片描述

3.9、往对象C中注入对象A

由于对象A已经在一级缓存中了,所以对象C通过getSingleton方法可以直接获取对象A的代理对象

在这里插入图片描述

三、流程分析

在上述示例代码中,对象A中注入对象B、对象C,而对象B和对象C中也会注入对象A,这就造成了循环依赖

  • Spring根据@SpringBootApplication注解中@ComponentScan扫描到了A类(被@Component注解修饰)

  • Spring先去Bean容器中获取对象A,但是发现对象A不在Spring容器中,那就来创建对象A;首先根据A类的构造函数创建对象A,但是对象A不会被放在三级缓存中
    在这里插入图片描述

  • 将匿名内部类放到第3级缓存singletonFactories中,在后面解决循环依赖时可以用到
    在这里插入图片描述

  • 在对象A中注入对象B
    在这里插入图片描述

  • Spring就去Bean容器中获取对象B,但是发现对象B不在Spring容器中,那就来创建对象B;和创建对象A一样,也是根据A类的构造函数创建对象A,然后准备往对象B中注入对象A
    在这里插入图片描述

  • Spring就去Bean容器中获取对象A,我们在上面将包含对象A信息的匿名内部类放到了第3级缓存singletonFactories中,现在我们可以把它取出来,之后调用它的getObject()方法,其实就是执行匿名内部类中的方法
    在这里插入图片描述

  • 我们来看下匿名内部类中的方法,图片下半部分是方法开始的地方,然后在往上看就可以;大家看下标号①,我们将生成代理对象后的原始对象存在earlyProxyReferences集合中,后续当对象A完成初始化方法后通过earlyProxyReferences集合进行判断,然后决定是否执行生成代理对象的方法,避免其他对象中注入的代理对象和Bean工厂中存储的代理对象不一致;大家再看下标号②,在A类中有一个test()方法上添加了@Transactional注解,当执行wrapIfNecessary()方法之后就会通过CGlib生成一个代理对象A;而原始对象A将被存储在earlyProxyReferences集合中
    在这里插入图片描述

  • 当代理对象A生成完成,相当于singletonFactory.getObject()执行完毕,此时singletonObject的值就是代理对象A,然后将代理对象A放到第2级缓存earlySingletonObjects中,并删除第3级缓存singletonFactories中存储的信息
    在这里插入图片描述

  • 上面通过getSingleton()方法获取到了代理对象A,然后交给对象B做注入,最终完成对象B的创建工作,然后将创建完成的对象B返回给对象A做注入,当对象B被注入到对象A之后,那就完成了populateBean()方法
    在这里插入图片描述

  • 然后初始化也是对象创建道路上的关键一环,现在来执行initializeBean()方法,生成代理对象的位置在该方法末尾的地方,即执行applyBeanPostProcessorsAfterInitialization()方法的地方,其中方法会执行到这个地方;在解决循环依赖(对象A和B相互依赖)的时候,我们把原始对象A已经放到了earlyProxyReferences集合中,所以此时可以从循环依赖中取到原始对象A,所以if判断的结果是false,因此就不会执行生成代理对象A的过程了,大家是否还记得,我们之前已经把代理对象A放到了第2级缓存earlySingletonObjects中了,我们在后续步骤中会从二级缓存中去除代理对象A,然后放到一级缓存中,往下慢慢看
    在这里插入图片描述

  • 此时exposedObject依然是原始对象A,然后往下执行getSingleton()方法来获取代理对象A
    在这里插入图片描述

  • 在解决循环依赖的时候,我们将代理对象放到了2级缓存earlySingletonObjects集合中,现在我们取出代理对象赋值给earlySingletonReference
    在这里插入图片描述

  • 然后doCreateBean()方法将代理对象A返回,然后createBean()方法在将代理对象A返回,这也标志着红框中匿名内部类中方法的执行结束,而匿名内部类方法的调用位置在getSingleton()方法中,也就是getObject()方法,此时singletonObject的值就是代理对象A
    在这里插入图片描述

  • 最后调用addSingleton()方法将代理对象A放入1级缓存中
    在这里插入图片描述
    在这里插入图片描述

  • Spring根据@SpringBootApplication注解中@ComponentScan扫描到了C类(被@Component注解修饰)

  • C类中注入了对象A,所以需要获取对象A,此时对象A已经放到了1级缓存中,所以可以直接取到代理对象A,从而在对象C中注入代理对象A
    在这里插入图片描述

四、参考文章

  • 一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2181930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

综合绩效考核系统源码,三级医院绩效管理系统源码,基于springboot、mybaits+avue技术开发,支持项目二开。

医院综合绩效考核系统源码 商业项目源码&#xff0c;支持二次开发 采用多维度综合绩效考核的形式&#xff0c;针对院内实际情况分别对工作量、KPI指标、科研、教学、管理等进行全面考核。医院可结合实际需求&#xff0c;对考核方案中各维度进行灵活配置&#xff0c;对各维度的权…

MPS---MP87180芯片layout设计总结

今天是一个特殊的日子&#xff0c;十一节前最后一天了&#xff0c;小编我还在迪拜出差中&#xff0c;而且组内也就剩下我一个人在值班了&#xff0c;来自韩国首尔的测试同事杰总提前一周就回韩国了&#xff0c;EE同事龟田一郎桑也是提前三天回日本东京去了&#xff0c;只有我最…

Brave编译指南2024 MacOS篇-构建与运行(六)

引言 在上一篇文章中&#xff0c;我们成功初始化了Brave浏览器的构建环境。现在&#xff0c;我们进入了这个编译指南的核心部分&#xff1a;实际构建Brave浏览器并运行它。这个过程将把我们之前准备的所有源代码和依赖项转化为一个可运行的浏览器实例。 1. 编译Brave浏览器 …

C++—vector的使用及实现

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 小伞的主页&#xff1a;xiaosan_blog 1.vector 1.1vector的介绍 cplusplus.com/reference/vector/vector/ 1.2vecto…

wpa_cli支持EAP-TTLS认证运行设计

wpa_cli支持EAP-TTLS认证运行设计 1 输入 1.1 启动wpa_supplicant 和 wpa_cli 在OpenHarmony开发板或华为开发机的命令行中输入 wpa_supplicant -Dnl80211 -c/data/service/el1/public/wifi/wpa_supplicant/wpa_supplicant.conf -gabstract:/data/service/el1/public/wifi/s…

【D3.js in Action 3 精译_026】3.4 小节 DIY 实战:基于 Mocha 在浏览器客户端测试 D3 线性比例尺

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

力扣(leetcode)每日一题 2286 以组为单位订音乐会的门票 | 线段树

2286. 以组为单位订音乐会的门票 题干 一个音乐会总共有 n 排座位&#xff0c;编号从 0 到 n - 1 &#xff0c;每一排有 m 个座椅&#xff0c;编号为 0 到 m - 1 。你需要设计一个买票系统&#xff0c;针对以下情况进行座位安排&#xff1a; 同一组的 k 位观众坐在 同一排座…

物联网实训室建设的必要性

物联网实训室建设的必要性 一、物联网发展的背景 物联网&#xff08;IoT&#xff09;是指通过信息传感设备&#xff0c;按照约定的协议&#xff0c;将任何物品与互联网连接起来&#xff0c;进行信息交换和通信&#xff0c;以实现智能化识别、定位、跟踪、监控和管理的一种网络…

c语言实例 068

大家好&#xff0c;欢迎来到无限大的频道 今天给大家带来的是c语言。 题目描述 创建一个单链表&#xff0c;进行存储数据并且打印 创建一个单链表的基本步骤包括定义链表节点的结构体、实现插入数据的功能&#xff0c;以及打印链表的内容。以下是一个简单的C语言示例&#…

QT将QBytearray的data()指针赋值给结构体指针变量后数据不正确的问题

1、问题代码 #include <QCoreApplication>#pragma pack(push, 1) typedef struct {int a; // 4字节float b; // 4字节char c; // 1字节int *d; // 8字节 }testStruct; #pragma pack(pop)#include <QByteArray> #include <QDebug>int main() {testStruct …

【C++前缀和 数论 贪心】2245. 转角路径的乘积中最多能有几个尾随零|2036

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 质数、最大公约数、菲蜀定理 贪心&#xff08;决策包容性) LeetCode2245. 转角路径的乘积中最多能有几个尾随零 给你一个二维整数数组 grid &#xff0c;大小为 m x …

【有啥问啥】二分图(Bipartite Graph)算法原理详解

二分图&#xff08;Bipartite Graph&#xff09;算法原理详解 引言 二分图&#xff08;Bipartite Graph&#xff09;&#xff0c;又称二部图&#xff0c;是图论中的一个重要概念。在实际应用中&#xff0c;二分图模型经常用于解决如匹配问题、覆盖问题和独立集问题等。本文将…

实验2思科网院项目2.7.2-packet-tracer---configure-single-area-ospfv2---实践练习

实践练习 2.7.2-packet-tracer---configure-single-area-ospfv2---实践练习physical-mode 实验拓扑 相关设备配置 实验目标: 第 1 部分&#xff1a;构建网络并配置设备的基本设置 第 2 部分&#xff1a;配置和验证单区域 OSPFv2 的基本部署 第 3 部分&#xff1a;优化和验…

【STM32】 TCP/IP通信协议(3)--LwIP网络接口

LwIP协议栈支持多种不同的网络接口&#xff08;网卡&#xff09;&#xff0c;由于网卡是直接跟硬件平台打交道&#xff0c;硬件不同则处理也是不同。那Iwip如何兼容这些不同的网卡呢&#xff1f; LwIP提供统一的接口&#xff0c;底层函数需要用户自行完成&#xff0c;例如网卡的…

动态时钟控件:Qt/C++ 项目代码解读

基于Qt的动态时钟控件项目。该项目展示了如何通过Qt的绘图系统绘制一个带有表盘背景、时针、分针、秒针、以及时间日期显示的时钟。同时&#xff0c;这个时钟控件支持背景切换&#xff0c;并且每秒钟刷新一次&#xff0c;实时显示当前时间。 项目结构与功能概述 该时钟控件主…

Redis接口访问优化

说明&#xff1a;之前写过一篇使用Redis接口访问的博客&#xff0c;如下。最近有相关需求&#xff0c;把代码拿出来后&#xff0c;做了一些优化&#xff0c;挺有意思的&#xff0c;本文介绍在原基础上 使用Redis实现接口防抖 优化 总的来说&#xff0c;这次使用Redis实现接口…

自动驾驶汽车横向控制方法研究综述

【摘要】 为实现精确、稳定的横向控制&#xff0c;提高车辆自主行驶的安全性和保障乘坐舒适性&#xff0c;综述了近年来自动驾驶汽车横向控制方法的最新进展&#xff0c;包括经典控制方法和基于深度学习的方法&#xff0c;讨论了各类方法的性能特点及在应用中的优缺点&#xff…

【初阶数据结构】详解插入排序 希尔排序(内含排序的概念和意义)

文章目录 前言1. 排序的概念及其应用1.1 排序的概念1.2 排序的应用 2. 插入排序2.1 基本思想2.2 插入排序的代码实现2.3 插入排序算法总结 3. 希尔排序3.1 基本思想3.2 希尔排序的代码实现3.3 希尔排序的特征总结 前言 初级数据结构系列已经进入到了排序的部分了。相信大家听到…

计算机毕业设计 服装生产信息管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

记录|Modbus-TCP产品使用记录【德克威尔】

目录 前言一、德克威尔1.1 实验图1.2 DECOWELL IO Tester 软件1.3 读写设置1.4 C#进行Modbus-TCP读写 更新时间 前言 参考文章&#xff1a; 使用的第二款Modbus-TCP产品。 一、德克威尔 1.1 实验图 1.2 DECOWELL IO Tester 软件 这也是自带模块配置软件的。下图就是德克威尔的…