spring循环依赖策略解析

发布时间 - 2026-01-11 03:11:57    点击率:

循环依赖

所谓循环依赖就是多个Bean之间依赖关系形成一个闭环,例如A->B->C->...->A 这种情况,当然,最简单的循环依赖就是2个Bean之间互相依赖:A->B(A依赖B), B->A(B依赖A) 。在Spring中,如果A->B,那么在创建A的过程中会去创建B,在创建B(或B的依赖)的过程中又发现B->A,这个时候就出现了循环依赖的现象。

循环依赖的解决

spring中的循环依赖只有当

1.Bean是单例,
2.通过属性注入的情况

这两个条件满足的情况下是没问题的。但是如果是通过构造器依赖,或者不是单例模式的情况下循环依赖就会抛出异常BeanCurrentlyInCreationException。下面从代码层面上解析一下为什么。

Prototype的循环依赖问题

为什么最先介绍Prototype的循环依赖呢,因为可以顺便介绍在Spring中创建Bean的流程核心流程:在AbstractFoctory的doGetBean的方法。这个方法很长,这里只写出核心逻辑,并在注解上注明了个人理解:

protected <T> T doGetBean(
  final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
  throws BeansException {
 
 final String beanName = transformedBeanName(name);
 Object bean;
 
 //尝试获取单例对象,因为spring大部分的bean都是单例的,所以这里先尝试能否获取。
 registered singletons.
 Object sharedInstance = getSingleton(beanName);
 //单例存在的情况下,那么beanName返回的肯定是单例类,但是这里还需要判断是不是FactoryBean
 if (sharedInstance != null && args == null) {
  ...
  //FactoryBean应该返回getObject()对象
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
 }
 
 else {
  //走到这里,有可能beanName是单例模式,但之前并没有实例化,或者是Prototype类型。
  //首先判断不是循环依赖,这里的循环依赖指的是Prototype类型
  if (isPrototypeCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
  }
 
 
  try {
   final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
   // 如果是单例,则创建单例模式
   if (mbd.isSingleton()) {
    // !!!这里是解决单例循环依赖的关键,后面再分析
    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
     @Override
     public Object getObject() throws BeansException {
      try {
       return createBean(beanName, mbd, args);
      }
      catch (BeansException ex) {
       throw ex;
      }
     }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
   }
 
   else if (mbd.isPrototype()) {
    // 原型模式,则创建一个新对象.
    Object prototypeInstance = null;
    try {
     /*这里是Prototype循环依赖的问题,会记录在map中beanName,
     如果在解决当前Bean的依赖过程中还依赖当前Bean,
     则说明了出现了循环依赖
     */
       beforePrototypeCreation(beanName);
       prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
     //对应beforePrototypeCreation(),从map中移除
       afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
   }
   ...
  }
 }
 
 ...
 return (T) bean;
}

可以看出,该流程中就考虑了Prototype的循环依赖的问题,只要在创建Prototype的Bean中出现循环依赖那么就抛出异常。但是在singleton的情况下,则通过另外的方式来解决。

Singleton的循环依赖之构造注入

在上面介绍中,出现了一个很关键的地方:

sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
 @Override
 public Object getObject() throws BeansException {
  try {
   return createBean(beanName, mbd, args);
  }
  catch (BeansException ex) {
   throw ex;
  }
 }
});

这个getSingleton涉及到了ObjectFactory这个接口类,这个接口的功能和FactoryBean类似,但是主要是用来解决循环依赖的。在初始化过程同决定返回的Singleton对象是。关于单例的对象的创建,又要介绍一下DefaultSingletonBeanRegistry这个类,这个类主要用来帮助创建单例模式,其中主要的属性:

/** 缓存创建的单例对象: bean名字 --> bean对象 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** 缓存单例的factory,就是ObjectFactory这个东西,: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** 也是缓存创建的单例对象,功能和singletonObjects不一样,
在bean构造成功之后,属性初始化之前会把对象放入到这里,
主要是用于解决属性注入的循环引用: bean name --> bean instance 
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** 记录在创建单例对象中循环依赖的问题,还记得Prototype中又记录创建过程中依赖的map吗?
在Prototype中只要出现了循环依赖就抛出异常,而在单例中会尝试解决 */
private final Set<String> singletonsCurrentlyInCreation =
  Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));

现在回过来看getSingleton(beanName, new ObjectFactory<Object>()这个方法的实现。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {

 synchronized (this.singletonObjects) {
  //尝试在singletonObjects中获取
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null) {
   //不存在则创建
   //把当前beanName加入到singletonsCurrentlyInCreation中
   beforeSingletonCreation(beanName);
   try {
    
    singletonObject = singletonFactory.getObject();
   }
   ...
   finally {
    ...
    //从singletonsCurrentlyInCreation中删除beanName
    afterSingletonCreation(beanName);
   }
  }
  return (singletonObject != NULL_OBJECT ? singletonObject : null);
 }
}

这段逻辑是不是和Prototype中解决循环类似,这里其实就是调用了ObjectFactory的getObject()获取对象,回过头去看前面代码,ObjectFactory的getObject()方法实际调用的是createBean(beanName, mbd, args)。说到createBean(beanName, mbd, args)又不得不说AbstractAutowireCapableBeanFactory这个类,主要功能就是完成依赖注入的Bean的创建,这个类的createBean方法代码如下,注意注解说明:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
 ...
 Object beanInstance = doCreateBean(beanName, mbdToUse, args);
 ...
}

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
   throws BeanCreationException {

 // 实例化bean
 BeanWrapper instanceWrapper = null;
 if (mbd.isSingleton()) {
  instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
 }
 if (instanceWrapper == null) {
  //如果没实例化则创建新的BeanWrapper
  //如果是通过构造器注入,这里是一个关键点
  /*
  因为在A初始化的时候发现构造函数依赖B,就会去实例化B,
  然后B也会运行到这段逻辑,构造函数中发现依赖A,
  这个时候就会抛出循环依赖的异常
  */
    instanceWrapper = createBeanInstance(beanName, mbd, args);
 }
 

 //如果当前是单例,并且allowCircularReferences为true(默认就是true,除非我们不希望Spring帮我们解决)
 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
   isSingletonCurrentlyInCreation(beanName));
 if (earlySingletonExposure) {
  /*
  !!!这里很重要,把构造成功,但属性还没注入的
  的bean加到singletonFactory中,这样再解决A的依赖
  过程中如果依赖A,就把这个半成品返回回去。
  */
  addSingletonFactory(beanName, new ObjectFactory<Object>() {
   @Override
   public Object getObject() throws BeansException {
    return getEarlyBeanReference(beanName, mbd, bean);
   }
  });
 }

 
 Object exposedObject = bean;
 try {
  //自动注入属性
  populateBean(beanName, mbd, instanceWrapper);
  if (exposedObject != null) {
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
 }
 ...

 return exposedObject;
}

注解已经注明了我的理解。就不再赘述

总结

上面代码是我一边debug一个写下的,现在写完了,根据自己的理解总结一下。

相关类说明

AbstractBeanFactory,这个类中包含了Bean创建的主要流程,在doGetBean这个方法中包含了对Prototype循环依赖处理。逻辑很简单,出现了循环依赖则直接抛出异常

DefaultSingletonBeanRegister 用于管理Singleton的对象的创建,以及解决循环依赖的问题,其中解决循环依赖的关键属性就是了earlySingletonObjects,他会在构造Singleton对象过程中暂时缓存构造成功,但属性还未注入的对象,这样就可以解决循环依赖的问题。

AbstractAutowireCapableBeanFactory,自动注入的相关逻辑,包自动注入的对象的创建、初始化和注入。但如果在调用构造函数中发现了循环依赖,则抛出异常

ObjectFactory,这个接口功能和FactoryBean类似,但是为了解决循环依赖,他决定了在获取的getSingleton()是一个完成品还是一个半成品。

思考

如果A--构造依赖->B,B--属性依赖-->A,例如:

@Component
public class BeanA {
 private BeanB beanB;

 @Autowired
 public BeanA(BeanB beanB) {
  this.beanB = beanB;
 }
}

@Component
public class BeanB {
 @Autowired
 private BeanA beanA;
}

这种情况会异常吗?提示:都有可能

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# spring循环依赖策略  # spring循环  # spring依赖策略  # Spring循环依赖正确性及Bean注入的顺序关系详解  # 浅谈Spring解决循环依赖的三种方式  # 浅谈Spring 解决循环依赖必须要三级缓存吗  # Spring循环依赖的三种方式(推荐)  # 详解Spring循环依赖的解决方案  # 深入理解Spring中的循环依赖  # 浅谈Spring如何解决循环依赖的问题  # 浅入浅出的讲解Spring循环依赖问题  # 抛出  # 过程中  # 出现了  # 是一个  # 情况下  # 就会  # 这段  # 这种情况  # 这个时候  # 会去  # 中又  # 自己的  # 的是  # 都是  # 都有  # 还没  # 闭环  # 也会  # 包含了  # 多个 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?  详解vue.js组件化开发实践  Laravel如何理解并使用服务容器(Service Container)_Laravel依赖注入与容器绑定说明  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  小米17系列还有一款新机?主打6.9英寸大直屏和旗舰级影像  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  EditPlus中的正则表达式 实战(2)  奇安信“盘古石”团队突破 iOS 26.1 提权  如何在橙子建站中快速调整背景颜色?  Laravel怎么处理异常_Laravel自定义异常处理与错误页面教程  如何挑选高效建站主机与优质域名?  Laravel如何实现一对一模型关联?(Eloquent示例)  三星、SK海力士获美批准:可向中国出口芯片制造设备  香港服务器建站指南:外贸独立站搭建与跨境电商配置流程  清除minerd进程的简单方法  Android Socket接口实现即时通讯实例代码  头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?  如何注册花生壳免费域名并搭建个人网站?  Windows10怎样连接蓝牙设备_Windows10蓝牙连接步骤【教程】  如何在阿里云虚拟服务器快速搭建网站?  使用Dockerfile构建java web环境  如何确认建站备案号应放置的具体位置?  C语言设计一个闪闪的圣诞树  Laravel如何操作JSON类型的数据库字段?(Eloquent示例)  网易LOFTER官网链接 老福特网页版登录地址  如何使用 jQuery 正确渲染 Instagram 风格的标签列表  如何在建站之星绑定自定义域名?  Laravel如何使用Passport实现OAuth2?(完整配置步骤)  如何在IIS中新建站点并解决端口绑定冲突?  详解阿里云nginx服务器多站点的配置  如何快速查询网站的真实建站时间?  Laravel Octane如何提升性能_使用Laravel Octane加速你的应用  Win10如何卸载预装Edge扩展_Win10卸载Edge扩展教程【方法】  如何快速打造个性化非模板自助建站?  Laravel如何发送邮件和通知_Laravel邮件与通知系统发送步骤  JS中对数组元素进行增删改移的方法总结  如何在七牛云存储上搭建网站并设置自定义域名?  Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】  如何用腾讯建站主机快速创建免费网站?  晋江文学城电脑版官网 晋江文学城网页版直接进入  微信小程序 require机制详解及实例代码  如何获取PHP WAP自助建站系统源码?  jQuery中的100个技巧汇总  Laravel怎么发送邮件_Laravel Mail类SMTP配置教程  百度输入法ai组件怎么删除 百度输入法ai组件移除工具  laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程  Laravel策略(Policy)如何控制权限_Laravel Gates与Policies实现用户授权  网站制作报价单模板图片,小松挖机官方网站报价?  Laravel用户认证怎么做_Laravel Breeze脚手架快速实现登录注册功能  Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知