啊,昨晚发版又出现了让有头大的循环依赖问题,按理说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
注解是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容器中增加了一个代理对象,也就是说beana
的beanb
并不是一个原始对象,而是一个代理对象接下来进行执行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 条评论) |