Spring如何解决循环依赖

在这里插入图片描述

博主 默语带您 Go to New World.
个人主页—— 默语 的博客👦🏻 优秀内容
《java 面试题大全》
《java 专栏》
《idea技术专区》
《spring boot 技术专区》
《MyBatis从入门到精通》
《23种设计模式》
《经典算法学习》
《spring 学习》
《MYSQL从入门到精通》数据库是开发者必会基础之一~
🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭
🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄之助。苟未尽善尽美,敬请批评指正,以资改进。!💻⌨


默语是谁?

大家好,我是 默语,别名默语博主,擅长的技术领域包括Java、运维和人工智能。我的技术背景扎实,涵盖了从后端开发到前端框架的各个方面,特别是在Java 性能优化、多线程编程、算法优化等领域有深厚造诣。

目前,我活跃在CSDN、掘金、阿里云和 51CTO等平台,全网拥有超过10万的粉丝,总阅读量超过1400 万。统一 IP 名称为 默语 或者 默语博主。我是 CSDN 博客专家、阿里云专家博主和掘金博客专家,曾获博客专家、优秀社区主理人等多项荣誉,并在 2023 年度博客之星评选中名列前 50。我还是 Java 高级工程师、自媒体博主,北京城市开发者社区的主理人,拥有丰富的项目开发经验和产品设计能力。希望通过我的分享,帮助大家更好地了解和使用各类技术产品,在不断的学习过程中,可以帮助到更多的人,结交更多的朋友.


我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。


循环依赖:其实就是互相引用,就好像同时相向过独木桥的感觉。简单描述下就是A服务依赖B服务,B服务依赖C服务,而C服务依赖A服务,如下图所示

上述情况,在Spring中,循环依赖似乎很常见但是并没有出现以下情况

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   dependencyApplication (field private cn.tinyice.demo.dependency.ServiceA cn.tinyice.demo.dependency.DependencyApplication.serviceA)
┌─────┐
|  serviceA (field private cn.tinyice.demo.dependency.ServiceB cn.tinyice.demo.dependency.ServiceA.serviceB)
↑     ↓
|  serviceB (field private cn.tinyice.demo.dependency.ServiceC cn.tinyice.demo.dependency.ServiceB.serviceC)
↑     ↓
|  serviceC (field private cn.tinyice.demo.dependency.ServiceA cn.tinyice.demo.dependency.ServiceC.serviceA)
└─────┘

那么Spring是如何解决这种问题呢?今天来分析一下。不过需要先熟悉下Bean的生命周期:宏观来看Bean的生命周期分为四步:1.实例化 2.属性设置 3.初始化 4.销毁。对依赖注入而言在第二步属性设置其实已经完成了,所以只需要关注前两步即可。

首先说明:Spring解决循环依赖解决的是单例的循环依赖。解决的方式就是找到对象的引用,属性设置即可。

核心类在DefaultSingletonBeanRegistry,先看下核心字段

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {


   /**
    * 单例注册表
    * 一级缓存,完整可用的单例Bean,平时GetBean获取的就是这里的对象
    * key = Bean名称
    * value= Bean实例
    */
   private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

   /**
    * 单例工厂注册表
    * 三级缓存,通过对象工厂获取对象,可以是代理对象,然后放入二级缓存中
    * key = Bean名称
    * value= 对象工厂
    */
   private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

   /**
    * 早期实例注册表(只进行了实例化、还未进行属性装配)
    * 二级缓存,缓存工厂中产生的对象,不用重复构建,在后续完善后会放入一级缓存中给开发者使用
    * key = Bean名称
    * value= Bean实例
    */
   private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
   
   ...
}   

Map比较多,针对循环依赖只需要关注以上3个Map(三个缓存)和一个方法

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 单例获取:尝试获取完整可用的单例Bean
  Object singletonObject = this.singletonObjects.get(beanName);
  // 单例不存在且该Bean处于创建中
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
	 synchronized (this.singletonObjects) {
		// 从实例化Bean中获取:尝试获取不完整的Bean
		singletonObject = this.earlySingletonObjects.get(beanName);
		// 不属于从实例化Bean且允许返回实例化的Bean的引用
		if (singletonObject == null && allowEarlyReference) {
		   // 获取改Bean的对象工厂
		   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
		   // 存在对象工厂
		   if (singletonFactory != null) {
			  // 触发Bean的获取流程
			  singletonObject = singletonFactory.getObject();
			  // 生产的单例对象放入实例化Bean中(二级缓存)
			  this.earlySingletonObjects.put(beanName, singletonObject);
			  // 从对象工厂中移除: 已经生产了实例化Bean则不再需要对象工厂了
			  this.singletonFactories.remove(beanName);
		   }
		}
	 }
  }
  return singletonObject;
}

这里涉及的三个缓存的使用顺序:单例对象通过三级缓存中对象工厂singletonFactory.getObject()来获取一个实例化的Bean,这个过程就是Bean的获取流程,后续将获取的Bean放入二级缓存earlySingletonObjects中,二级缓存中的Bean在完善之后放入到一级缓存singletonObjects中;也就是这个Bean经过获取、完善两步最终提供给开发者使用,无论中间经历了什么,该Bean的引用地址不再发生变化,任何一步获取到引用即可用于依赖注入,二者三个缓存其实都是为了优化获取流程,三级缓存是保证Bean的获取流程完整;二级缓存是优化三级缓存,不必重复获取,提高性能;一级缓存是用于对开发者提供完整的单例。

简要介绍下singletonObject = singletonFactory.getObject();这个过程支持Bean的AOP处理,同时allowCircularReferences=true才会允许创建不完整的Bean(二级缓存中的Bean);这个singletonFactory构建在doCreateBean中,

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这里关注的不是匿名的构造方法,而是Bean,因为getEarlyBeanReference几乎不对入参Bean进行处理,这个Bean就是getObject返回的bean

final Object bean = instanceWrapper.getWrappedInstance();

没错,这个Bean就是实例化后的Bean,此时刚刚实例化,依赖注入时只需要引用即可,这里正好可以满足。这里正是解决循环依赖的核心。

再说下循环依赖的注入步骤

@Autowired、@Value

会引入AutowiredAnnotationBeanPostProcessor在属性设置populateBean过程中,会调用其postProcessProperties方法

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 查找注入元数据,会找到要注入的元素,存入集合中 Collection<InjectedElement> injectedElements
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
	    // 开始注入,InjectedElement这里为AutowiredFieldElement
		metadata.inject(bean, beanName, pvs);
	}
	... 
	return pvs;
}

AutowiredFieldElement:字段注入

字段值Value的获取

value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

这里的beanFactory是DefaultListableBeanFactory

result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);

继续

instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);

继续

beanFactory.getBean(beanName)

这里就开始去BeanFactory获取Bean,由于是循环依赖,所以会循环加载Bean,而终止这个循环的条件就是上面所述的:获取Bean的实例化对象。无论怎么循环,首先触发注入的对象一定先实例化,这个实例化的存在就是终结循环的条件。 其他注入方式类似,只是实现方式不一样,不再过多描述。

举个例子,看下图示基本就完全了解了。

@Slf4j
@Service
public class ServiceA {

    @Autowired
    private ServiceB serviceB;

    public void dependencyName() {
        log.info(serviceB.getClass().getSimpleName());
    }
}
@Slf4j
@Service
public class ServiceB {

    @Autowired
    private ServiceC serviceC;

    public void dependencyName() {
        log.info(serviceC.getClass().getSimpleName());
    }
}
@Slf4j
@Service
public class ServiceC {

    @Autowired
    private ServiceA serviceA;


    public void dependencyName() {
        log.info(serviceA.getClass().getSimpleName());
    }
}

测试类

@SpringBootApplication(
        exclude = DataSourceAutoConfiguration.class,
        scanBasePackages = {"cn.tinyice.demo.dependency"}
)
@RestController
public class DependencyApplication {

    public static void main(String[] args) {
        SpringApplication.run(DependencyApplication.class, args);
    }

    @Autowired
    private ServiceA serviceA;
    @Autowired
    private ServiceB serviceB;
    @Autowired
    private ServiceC serviceC;

    @GetMapping("/name")
    public void dependency() {
        serviceA.dependencyName();
        serviceB.dependencyName();
        serviceC.dependencyName();
    }
}

控制台

Tomcat started on port(s): 8080 (http) with context path ''
Started DependencyApplication in 1.922 seconds (JVM running for 3.19)
Initializing Spring DispatcherServlet 'dispatcherServlet'
Initializing Servlet 'dispatcherServlet'
Completed initialization in 6 ms
ServiceB
ServiceC
ServiceA

依赖注入流程

这里ServiceA 依赖ServiceB,ServiceB依赖ServiceC,而ServiceC依赖ServiceA,在A->B过程中,因为B未实例化,会触发B的getBean,又因为C未实例化,会触发C的实例化,而C—>A时会发现A的实例化对象ServiceA instance因而完成C的创建,随后完成B的创建,进而完成A的创建,至此这个循环依赖注入完毕,不会出现任何异常。

Spring BeanFactory 的allowCircularReferences属性可以关闭循环依赖的注入,默认=true,允许循环依赖。

在这里插入图片描述


🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥

如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进📓;(联系微信:Solitudemind )

点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

在这里插入图片描述

更多推荐