Spring如何解决循环依赖
这里ServiceA 依赖ServiceB,ServiceB依赖ServiceC,而ServiceC依赖ServiceA,在A->B过程中,因为B未实例化,会触发B的getBean,又因为C未实例化,会触发C的实例化,而C—>A时会发现A的实例化对象ServiceA instance因而完成C的创建,随后完成B的创建,进而完成A的创建,至此这个循环依赖注入完毕,不会出现任何异常。希望通过我的分享,
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技术核心学习团队。一起探索科技的未来,共同成长。
更多推荐
所有评论(0)