Spring的循环依赖和三级缓存

简介: Spring的循环依赖和三级缓存

解决循环依赖


假设有一种下面的情况,A中有B,B中有A


@Data
public class A {
  private B b;
  public A() {System.out.println("A 无参构造器。。。");}
  public void speak() {System.out.println("------AAA---------");}
}
@Data
public class B {
  public B() {System.out.println("B 无参构造器。。。");}
  private A a;
  public void speak() {System.out.println("------BBB---------");}
}


图片分析


20200809223233559.png

代码分析


创建的A的时候调用doCreateBean方法

1)调用A无参构造方法创建Bean

2)把该bean对象添加到三级缓存中(在下面的代码中有注释)

3)Bean的属性赋值,A的里面引用了B,所以此时会调用doCteateBean(B)


//AbstractAutowireCapableBeanFactory
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
      {
    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
      //省略
    }
    if (instanceWrapper == null) {
        //1)调用无参构造方法创建Bean
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
    }
    synchronized (mbd.postProcessingLock) {
      //省略
    }
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
        //省略
      }
      //2)把该bean对象添加到三级缓存中,注意getEarlyBeanReference方法,特别有用
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        //3)Bean的属性赋值
      populateBean(beanName, mbd, instanceWrapper);
      //4)处理aware接口、applyBeanPostProcessorsBeforeInitialization、initMethod
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
        //省略
      }
      else {
        //省略
      }
    }
    if (earlySingletonExposure) {
      //省略
    }
    // Register bean as disposable.
    try {
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
      //省略
    }
    return exposedObject;
  }
}


此时在创建B的时候调用getBean(A),然后会走到下面代码的地方,从三级缓存中获取到A(B=null),返回该不完整的A的地址,然后B创建成功,然后继续创建A,然后A也创建成功。


-------------------------源码1


//DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //从一级缓存中获取,即IOC容器,即完整的Bean对象
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
                //从二级缓存中获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
                    //从三级缓存中获取
          ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
          if (singletonFactory != null) {
            singletonObject = singletonFactory.getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
          }
        }
      }
    }
    return singletonObject;
  }


循环依赖总结


      (1)创建A的时候调用A的无参构造方法,然后在把得到的地址A(B=null)放入到三级缓存中,然后填充自己的属性B,也就会创建B;


      (2)当创建B的时候,填充自己的属性A,从三级缓存中拿到A(B=null)地址,然后B创建成功;


      (3)此时回到(1),此时拿到B,然后完善A,创建A成功。


      (4)因为在(2)中拿到的是A的地址,所以在(3)中完善A在B中是一个。      


三级缓存


疑问


个人感觉二级缓存足矣,为什么还要三级缓存?


反驳疑问


假设下面的场景:只有singletonObject(第一级缓存)和singletonFactory (第三级缓存),即没有earlySingletonObjects(第二级缓存)


如果有这么一种情况A(B),B(A),还有一个AOP是关注A的某个方法


此时的逻辑为:


1)创建A


2)把A(B=null)的地址(abc)存入singletonFactory缓存中


3)创建B


4)B在赋值a属性的时候,在singletonFactory缓存中拿出A的地址(abc)并且赋值给属性a(左边这句话是错的)(这就是三级缓存的关键),


    4.1)没有AOP的时候,确实是存的a的地址,没错,返回的也是a的地址。


     4.2)如果有AOP,确实存进去的是a的地址,但是返回的已经不是A的地址了,是A的代理对象地址(看源码2,3,4)。


总结:此时就出现问题了,如果没有earlySingletonObjects(第二级缓存),那么每次在singletonFactory (第三级缓存)中拿到的A对象都会创建创建一个代理对象,即每次向依赖A的对象中赋的值都是不同的代理对象,那么就不符合单例模式了。


-------------------------源码2


protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
          SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
          exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
        }
      }
    }
    return exposedObject;
  }


-------------------------源码3


//AbstractAutoProxyCreator
@Override
  public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
        //跟进去
    return wrapIfNecessary(bean, beanName, cacheKey);
  }


-------------------------源码4


//AbstractAutoProxyCreator
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
    }
    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //返回了一个新对象,新地址
            //返回了一个新对象,新地址
            //返回了一个新对象,新地址
      Object proxy = createProxy(
          bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
    }
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
  }


总结


1)在没有AOP的情况下二级缓存足矣解决循环依赖,三级缓存更能解决问题。

2)三级缓存其实也是解决循环依赖的,是解决带AOP的循环依赖的,如上文中举的例子。如果您查的三级缓存资料没有说AOP,个人感觉这篇文章写的不是很充实。

 

本文没有回答的疑问


疑问1


上问中反驳二级缓存不能解决带AOP的循环依赖问题时,是把earlySingletonObjects(第二级缓存)去掉;如果我说我去掉singletonFactory (第三级缓存),那该如何反驳二级缓存不能解决带AOP的循环依赖问题呢???


疑问2


就拿上问中举的例字来说,A依赖B,B依赖A,有一个关注A的AOP。

下面是创建Bean声明周期的一段代码,以创建A为例


//AbstractAutowireCapableBeanFactory
protected Object doCreateBean{
//创建A
Object exposedObject = bean;
    try {
            //初始化A,因为A中有属性B,此时去创建B,然后把A的代理对象存入earlySingletonObjects缓存中,B创建完毕,然后又回到此处继续初始化A
      populateBean(beanName, mbd, instanceWrapper);
            //为非代理对象A执行aware接口等等
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
      //省略
      }
    }
    if (earlySingletonExposure) {
            //在earlySingletonObjects中拿到代理对象A
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
        if (exposedObject == bean) {
                    //把exposedObject由指向非代理对象A变为指向代理对象A,那么
                    //exposedObject = initializeBean(beanName, exposedObject, mbd);
                    //我认为是白做了,我不清楚这个地方???????????????
          exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
          String[] dependentBeans = getDependentBeans(beanName);
          Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
          for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
              actualDependentBeans.add(dependentBean);
            }
          }
          if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                "Bean with name '" + beanName + "' has been injected into other beans [" +
                StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                "] in its raw version as part of a circular reference, but has eventually been " +
                "wrapped. This means that said other beans do not use the final version of the " +
                "bean. This is often the result of over-eager type matching - consider using " +
                "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
          }
        }
      }
}


如果有知道上面两个问题答案的,可以在下问中评论,一起学习,共同进步

目录
相关文章
|
6天前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
19天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
58 2
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
190 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
31 1
|
2月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
40 4
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
39 1
|
3月前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
3月前
|
缓存 NoSQL Java
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
Spring Cache 是 Spring 提供的简易缓存方案,支持本地与 Redis 缓存。通过添加 `spring-boot-starter-data-redis` 和 `spring-boot-starter-cache` 依赖,并使用 `@EnableCaching` 开启缓存功能。JetCache 由阿里开源,功能更丰富,支持多级缓存和异步 API,通过引入 `jetcache-starter-redis` 依赖并配置 YAML 文件启用。Layering Cache 则提供分层缓存机制,需引入 `layering-cache-starter` 依赖并使用特定注解实现缓存逻辑。
974 1
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
|
3月前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
3月前
|
缓存 Java Spring
Spring缓存实践指南:从入门到精通的全方位攻略!
【8月更文挑战第31天】在现代Web应用开发中,性能优化至关重要。Spring框架提供的缓存机制可以帮助开发者轻松实现数据缓存,提升应用响应速度并减少服务器负载。通过简单的配置和注解,如`@Cacheable`、`@CachePut`和`@CacheEvict`,可以将缓存功能无缝集成到Spring应用中。例如,在配置文件中启用缓存支持并通过`@Cacheable`注解标记方法即可实现缓存。此外,合理设计缓存策略也很重要,需考虑数据变动频率及缓存大小等因素。总之,Spring缓存机制为提升应用性能提供了一种简便快捷的方式。
50 0