首页 > 作文

深度解析SpringBoot中@Async引起的循环依赖

更新时间:2023-04-04 23:25:13 阅读: 评论:0

目录
事故时间线猜想什么是循环依赖什么是@async

啊,昨晚发版又出现了让有头大的循环依赖问题,按理说spring会为我们解决循环依赖,但是为什么还会出现这个问题呢?为什么在本地、uat以及pre环境都没有出现这个问题,但是到了prod环境就出现了这个问题呢?本文将从事故时间线、及时止损、复盘分析等几个方面为大家带来详细的分析,干货满满!

事故时间线

本着"先止损、后复盘分析"的原则,我们来看一下这次发版事故的时间线。

2021年11月16日晚23点00分00秒开始发版,此时集团的devops有点慢

2021年11月16日晚23点03分01秒,收到发版失败的消息,登录服务器发现发生了循环依赖,具体错误如下图,从日志中可以看到是datacollectionndmessagervice这个bean出现了循环依赖

问题发现了就需要先解决,然后再去分析为什么。看到这个报错日志我心里也大概知道是为什么了,所以很快就解决了,解决方案如下:给datacollectionndmessagervice加上@lazy注解

2021年11月16日晚23点07分16秒,使用重新集成的代码开始发版,大概10分钟后线上节点全部发版完成。从时间线来看从发现问题到解决问题,前后一共用了接近15分钟(这期间代码集成和发布用了过多的时间),也算是做到了及时止损,没有让问题继续扩大。

猜想

我大胆的猜想是因为打了@aysnc注解的bean生成了对象的代理,导致spring bean最终加载的不是一个原始对象导致了此次问题的发生,那么对不对呢,接下来我们通过源码详细分析一下。

什么是循环依赖

所谓循环依赖就是spring ioc容器在加载bean时会按照顺序加载,先去实例化 beana。然后发现 beana 依赖于 beanb,接在又去实例化 beanb。实例化 beanb 时,发现 beanb 又依赖于 beana。如果容器不处理循环依赖的话,容器会无限执行上面的流程,直到内存溢出,程序崩溃,所以这个时候就会抛出beancurrentlyincreationexception异常,也就是我们常说的循环依赖,下面是两种常见循环依赖的场景。

几个bean之间的循环依赖

@componentpublic class a {    @autowired    private b b;}@componentpublic class b {    @autowired    private 监狱基础知识c c;}@componentpublic class c {    @autowired    private a a;}

效果图如下:

自己依赖自己

@componentpublic class a {    @autowired    private a a;}

效果图如下:

spring是如何解决循环依赖的

首先spring维护了三个map,也就是我们通常说的三级缓存

singletonobjects:俗称单例池,缓存创建完成的单例beansingletonfactories:映射创建bean的原始工厂earlysingletonobjects:映射bean的早期引用,也就是说这个map里的bean不是完整的,只是完成了实例化,但还没有初始化

spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonobjects),二级缓存为早期曝光对象earlysingletonobjects,三级缓存为早期曝光对象工厂(singletonfactories)。

当a、b两个类发生循环引用时,在a完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果a被aop代理,那么通过这个工厂获取到的就是a代理后的对象,如果a没有被aop代理,那么这个工厂获取到的就是a实例化的对象

当a进行属性注入时,会去创建b,同时b又依赖了a,所以创建b的同时又会去调用getbean(a)来获取需要的依赖,此时的getbean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getobject方法来获取到对应的对象,得到这个对象后将其注入到b中。

紧接着b会走完它的生命周期流程,包括初始化、后置处理器等。当b创建完后,会将b再注入到a中,此时a再完成它的整个生命周期。至此,循环依赖结束!

简单一句话说:先去缓存里找bean,没有则实例化当前的bean放到map,如果有需要依赖当前bean的,就能从map取到。

什么是@async

@async注解是spring为我们提供的异步调用的注解,@async可以作用到类或者方法上,标记了@async注解的方法将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。从源码中可以看到标记了@async注解的方法会被提交到org.springframework.core.task.taskexecutor中异步执行。

或者我们可以通过value来指定使用哪个自定义线程池,比如这样子:

@async("asynctaskexecutor")

被@async标记的bean注入时机

我们从源码的角度来看一下被@async标记的bean是如何注入到spring容器里的。在我们开启@enableasync注解之后代表可以向spring容器中注入asyncannotationbeanpostprocessor,它是一个后置处理器,我们看一下他的类图。

真正创建代理对象的代码在abstractadvisingbeanpostprocessor中的postprocessafterinitialization方法中,以下代码有所删减,只保留核心逻辑代码

// 这个map用来缓存所有被postprocessafterinitialization这个方法处理的beanprivate final map<class<?>, boolean> eligiblebeans = new concurrenthashmap<>(256);// 这个方法主要是为打了@async注解的bean生成代理对象@overridepublic object postprocessafterinitialization(object bean, string beanname) {// 这里是重点,这里返回trueif (iligible(bean, beanname)) {// 工厂模式生成一个proxyfactoryproxyfactory proxyfactory = prepareproxyfactory(bean, beanname);if (!proxyfactory.isproxytargetclass()) {evaluateproxyinterfaces(bean.getclass(), proxyfactory);}// 切入切面并创建一个代理对象proxyfactory.addadvisor(this.advisor);customizeproxyfactory(proxyfactory);return proxyfactory.getproxy(getproxyclassloader());}// no pr河南护理学院oxy needed.return bean;}
protected boolean iligible(class<?> targetcl四年级上册英语第一单元ass) {// 首次从eligiblebeans这个map中一定是拿不到的boolean eligible = this.eligiblebeans.get(targetclass);if (eligible != null) {return eligible;}// 如果没有advisor,也就是切面,直接返回falif (this.advisor == null) {return fal;}// 这里判断asyncannotationadvisor能否切入,因为我们的bean是打了@aysnc注解,这里是一定能切入的,最终会返回trueeligible = aoputils.canap政治立场ply(this.advisor, targetclass);this.eligiblebeans.put(targetclass, eligible);return eligible;}

至此打了@aysnc注解的bean就创建完成了,结果是生成了一个代理对象

循环依赖到底是怎么生成的

经过上面的源码分析,我们可以知道有@aysnc注解的bean最后生成了一个代理对象,我们结合spring bean创建的流程来分析这次问题。

beana开始初始化,beana实例化完成后给beana的依赖属性beanb进行赋值beanb开始初始化,beanb实例化完成后给beanb的依赖属性beana进行赋值因为beana是支持循环依赖的,所以可以在earlysingletonobjects中可以拿到beana的早期引用的,但是因为beanb打了@aysnc注解并不能在earlysingletonobjects中可以拿到早期引用接下来执行执行initializebean(object existingbean, string beanname)方法,这里beana可以正常实例化完成,但是因为beanb打了@aysnc注解,所以向spring ioc容器中增加了一个代理对象,也就是说beanabeanb并不是一个原始对象,而是一个代理对象接下来进行执行docreatebean方法时对进行检测,以下代码有所删减,只保留核心逻辑代码
protected object docreatebean(final string beanname, final rootbeandefinition mbd, final @nullable object[] args)throws beancreationexception {if (earlysingletonexposure) {object earlysingletonreference = getsingleton(beanname, fal);if (earlysingletonreference != null) {if (expodobject == bean) {expodobject = earlysingletonreference;}el if (!this.allowrawinjectiondespitewrapping && hasdependentbean(beanname)) {string[] dependentbeans = getdependentbeans(beanname);t<string> actualdependentbeans = new linkedhasht<>(dependentbeans.length);// 重点在这里,这里会遍历所有依赖的bean,如果beana依赖beanb和缓存中的beanb不相等// 也就是说beana本来依赖的是一个原始对象beanb,但是这个时候发现beanb是一个代理对象,就会增加到actualdependentbeansfor (string dependentbean : dependentbeans) {if (!removesingletonifcreatedfortypecheckonly(dependentbean)) {actualdependentbeans.add(dependentbean);}}// 发现actualdependentbeans不为空,就发生了我们最开始截图的错误if (!actualdependentbeans.impt怎样修改路由器密码y()) {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 u 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.");}}}}// register bean as disposable.try {registerdisposablebeanifnecessary(beanname, bean, mbd);}catch (beandefinitionvalidationexception ex) {throw new beancreationexception(mbd.getresourcedescription(), beanname, "invalid destruction signature", ex);}return expodobject;}

解决循环依赖的正确姿势

@lazy注解代码优化,不要让@async的bean参与循环依赖

至此我们就知道为什么发生了此次问题。

到此这篇关于深度解析springboot中@async引起的循环依赖的文章就介绍到这了,更多相关springboot中@async循环依赖内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

本文发布于:2023-04-04 23:25:11,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/bada2d3dc5559861181fc24507fdb1b9.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:深度解析SpringBoot中@Async引起的循环依赖.doc

本文 PDF 下载地址:深度解析SpringBoot中@Async引起的循环依赖.pdf

标签:对象   注解   缓存   实例
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图