Springboot循环依赖实践纪实

测试的Springboot版本: 2.6.4,禁止了循环依赖,但是可以通过application.yml开启(哈哈)
@Lazy注解解决循环依赖 情况一:只有简单属性关系的循环依赖
涉及的Bean:

  • ASerivce及其实现类ASerivceImpl
  • BSerivce及其实现类BSerivceImpl
com.example.demo.service.AService
package com.example.demo.service; public interface AService { void zaWaLuDo(); }

com.example.demo.service.impl.AServiceImpl
package com.example.demo.service.impl; import com.example.demo.service.AService; import com.example.demo.service.BService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AServiceImpl implements AService {@Autowired public BService bService; @Override public void zaWaLuDo(){ System.out.println("ToKiOToMaLei!"); } }

com.example.demo.service.BService
package com.example.demo.service; public interface BService {}

com.example.demo.service.impl.BServiceImpl
package com.example.demo.service.impl; import com.example.demo.service.AService; import com.example.demo.service.BService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BServiceImpl implements BService { @Autowired public AService aService; }

此时ASerivceBService构成循环依赖的关系:
Springboot循环依赖实践纪实
文章图片

测试类:com.example.demo.service.AServiceTest
package com.example.demo.service; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class AServiceTest { @Autowired AService aService; @Test public void test(){ aService.zaWaLuDo(); }}

此时运行test方法,将会报错:
java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248) at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363) ......省略..... Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AServiceImpl': Unsatisfied dependency expressed through field 'bService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BServiceImpl': Unsatisfied dependency expressed through field 'aService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:659) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)

最重要的一句应该是:
美观处理过: Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AServiceImpl': Unsatisfied dependency expressed through field 'bService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BServiceImpl': Unsatisfied dependency expressed through field 'aService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?

Spring提醒我们可能存在circular reference,就是大名鼎鼎的循环依赖。
解决办法 在其中任意一个属性注入@Autowired上加入懒加载@Lazy即可跑通,比如在AService的实现类中加入:
package com.example.demo.service.impl; import com.example.demo.service.AService; import com.example.demo.service.BService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service public class AServiceImpl implements AService {@Autowired @Lazy //懒加载 public BService bService; @Override public void zaWaLuDo(){ System.out.println("ToKiOToMaLei!"); } }

此时,运行测试方法test()的运行结果就是:
ToKiOToMaLei!

说明aService.zaWaLuDo()方法执行成功
源码分析
参考:https://www.zhihu.com/question/438247718
Springboot循环依赖实践纪实
文章图片

主要是靠Spring中(人为定义)的三级缓存有关:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
第一级缓存:**Map singletonObjects**
第一级缓存的作用?
  • 用于存储单例模式下创建的Bean实例(已经创建完毕)。
  • 该缓存是对外使用的,指的就是使用Spring框架的程序员。
存储什么数据?
  • K:bean的名称
  • V:bean的实例对象, 或者说:“成品”对象(有代理对象则指的是代理对象,已经创建完毕)
第二级缓存:**Map earlySingletonObjects**
第二级缓存的作用?
  • 用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)。
  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
存储的数据:
  • K:bean的名称
  • V:bean的实例对象,“半成品”对象(有代理对象则指的是代理对象,该Bean还在创建中)
第三级缓存:**Map> singletonFactories**
第三级缓存的作用?
  • 通过ObjectFactory对象来存储单例模式下提前暴露的Bean实例的引用(正在创建中)。
  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
  • 此缓存是解决循环依赖最大的功臣
存储什么数据?
  • K:bean的名称
  • V:ObjectFactory,该对象持有提前暴露的bean的引用
一、注入AService时,首先进入org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean:
// Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } ... }

看看getSingleton方法的原型,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)
public Object getSingleton(String beanName, ObjectFactory singletonFactory)

所以此时doGetBean方法会进入lambda方法中的,调用createBean方法来得到一个ObjectFactory
接着我们进入到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorydoCreateBean方法, 打上断点看看:
  1. beanName='AServiceImpl'的时候,先根据反射创建了一个Object类的AServiceImpl的bean,里面的BServicenull
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){ ...省略... Object bean = instanceWrapper.getWrappedInstance(); //ASericeImpl@4686 Class beanType = instanceWrapper.getWrappedClass(); //beanType = "class com.example.demo.service.impl.AServiceImpl" ...省略... }

  1. 判断该bean是否已经被提前暴露
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ...省略...//判断该bean是否已经被提前暴露 //Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); //如果是,就调用addSingletonFactory方法, if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } ...省略... }

  1. 若没有被提前暴露,就进入到语句:
// Initialize the bean instance. Object exposedObject = bean; try { //调用populateBean方法后,AService中的BService属性就不再是null,而是一个$Proxy@4981$, //应该是个代理的对象,解决注入的燃眉之急 populateBean(beanName, mbd, instanceWrapper); //做一些初始化的操作 exposedObject = initializeBean(beanName, exposedObject, mbd); }

  1. 将该bean暴露
// Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); }

  1. 接着就将其返回
return exposedObject;

此时,exposedObject对象里的bService还是$Proxy$
Springboot循环依赖实践纪实
  1. BServiceImpl
package com.example.demo.service.impl; import com.example.demo.service.AService; import com.example.demo.service.BService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BServiceImpl implements BService { @Autowired public AService aService; @Override public void starPuLaXin() { System.out.println("Za WaLuDo!"); } }

我们先在执行aServuce,zaWaLuDo()之前打个断点看看此时的aService是什么情况:
Springboot循环依赖实践纪实
  • com.example.demo.service.impl.DServiceImpl
package com.example.demo.service.impl; import com.example.demo.service.CService; import com.example.demo.service.DService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class DServiceImpl implements DService { private CService cService; @Autowired public DServiceImpl(CService cService) { this.cService = cService; } }

  • com.example.demo.service.CServiceTest
package com.example.demo.service; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class CServiceTest { @Autowired CService cService; @Test public void test(){ cService.goldExperience(); } }

运行测试方法,同样报循环依赖的错误。
解决方法 在参数里添加@Lazy方法:
package com.example.demo.service.impl; import com.example.demo.service.CService; import com.example.demo.service.DService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service public class CServiceImpl implements CService {private DService dService; @Autowired public CServiceImpl(@Lazy DService dService) { //参数上添加了@Lazy方法 this.dService = dService; }@Override public void goldExperience() { System.out.println("MUDAMUDAMUDAMUDA!!!!"); } }

源码分析 跟情况一一样,也是通过注入一个"假"的对象解决:
Springboot循环依赖实践纪实
此时由进入到了老朋友org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法中,返回了一个CServiceImpl对象给上面的resolveFieldValue(field, bean, beanName); 接着就进入到field.set(bean, value); 中将其注入,那么神奇的事情肯定是发生在beanFactory.getBean(beanName);
老办法,再打个断点,回到取CServiceImpl对象的时候看看:
Springboot循环依赖实践纪实
文章图片

  1. 在创建cService调用doCreateBean方法,执行了addSingletonFactory
{ addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); }

三级缓存this.singletonFactories 中便存入了“半成品”对象的自己:
Springboot循环依赖实践纪实
  • cService执行到populateBean的时候,旋即进入到了dServicedoCreateBean
  • dService通过addSingletonFactory也往三级缓存this.singletonFactories 中便存入了“半成品”对象的自己,此时c、d都在三级缓存this.singletonFactories里:
    Springboot循环依赖实践纪实
    文章图片

    dService执行到getSingleton后:
  • // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); }

    getSingleton内部执行了addSingleton_(_beanName, singletonObject_)_之后,便把自己写入了三级缓存this.singletonObjects中,并把半成品的cService注入到自己中,形如:Springboot循环依赖实践纪实
  • 之后就回到了cService->populateBean的执行,最终去到了field.set_(_bean, value_)_中,此时bean为cService, value为dService(内部的cServicedService仍未空),执行完之后,就链接上了!神奇!:Springboot循环依赖实践纪实
    文章图片

    可以看出,互相依赖的两个对象有三种状态:
    1. 只有“存在”,没有内部的“半成品”形态一对象
    2. 注入了“半成品”形态一对象的“半成品”形态二对象
    3. 注入了“半成品”形态二对象的完全体“成品”对象
    因有客观上有三种状态的对象,所以才利用三级缓存来分别存储,比较科学的说明如下:
    三级缓存 singletonObjects
    一级缓存, Cache of singleton objects bean name --> bean instance。 存放完整对象。
    earlySingletonObjects
    二级缓存, Cache of early singleton objects bean name --> bean instance 提前曝光的BEAN缓存。 存放半成品对象。
    singletonFactories
    三级缓存, Cache of singleton factories bean name --> ObjectFactory。需要的对象被代理时,就必须使用三级缓存(否则二级就够了)。解决循环依赖中存在aop的问题 存放 lambda 表达式和对象名称的映射。
    作者:JavaEdge
    链接:https://www.zhihu.com/question/438247718/answer/2331910821
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    参考
    1. https://www.zhihu.com/question/438247718
    2. https://blog.csdn.net/qq_18298439/article/details/88818418
    【Springboot循环依赖实践纪实】本文仅为分享学习经历,欢迎批评指正

      推荐阅读