<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
說明
本文介紹SpringBoot中的@Async導致迴圈依賴失敗的原因及其解決方案。
概述
我們知道,Spring解決了迴圈依賴問題,但Spring的非同步(@Async)會使得迴圈依賴失敗。本文將用範例來介紹其原因和解決方案。
啟動類
啟動類新增@EnableAsync以啟用非同步功能。
package com.knife; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
Service
A
package com.knife.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class A { @Autowired private B b; @Async public void print() { System.out.println("Hello World"); } }
B
package com.knife.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class B { @Autowired private A a; }
Controller
package com.knife.controller; import com.knife.service.A; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired private A a; @GetMapping("/test") public String test() { a.print(); return "test success"; } }
啟動:(報錯)
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
... 20 common frames omitted
@EnableAsync開啟時向容器內注入AsyncAnnotationBeanPostProcessor,它是一個BeanPostProcessor,實現了postProcessAfterInitialization方法。建立代理的動作在抽象父類別AbstractAdvisingBeanPostProcessor上:
// 這個方法主要是為有@Async 註解的 bean 生成代理物件 @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (this.advisor == null || bean instanceof AopInfrastructureBean) { // Ignore AOP infrastructure such as scoped proxies. return bean; } // 如果此Bean已經被代理了(比如已經被事務那邊給代理了~~) if (bean instanceof Advised) { Advised advised = (Advised) bean; if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) { // Add our local Advisor to the existing proxy's Advisor chain... // beforeExistingAdvisors決定這該advisor最先執行還是最後執行 // 此處的advisor為:AsyncAnnotationAdvisor 它切入Class和Method標註有@Aysnc註解的地方~~~ if (this.beforeExistingAdvisors) { advised.addAdvisor(0, this.advisor); } else { advised.addAdvisor(this.advisor); } return bean; } } // 若不是代理物件,則進行處理 if (isEligible(bean, beanName)) { //copy屬性 proxyFactory.copyFrom(this); 工廠模式生成一個新的 ProxyFactory ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName); // 如果沒有采用CGLIB,就去探測它的介面 if (!proxyFactory.isProxyTargetClass()) { evaluateProxyInterfaces(bean.getClass(), proxyFactory); } // 切入切面並建立一個getProxy 代理物件 proxyFactory.addAdvisor(this.advisor); customizeProxyFactory(proxyFactory); return proxyFactory.getProxy(getProxyClassLoader()); } // No proxy needed. return bean; } protected boolean isEligible(Object bean, String beanName) { return isEligible(bean.getClass()); } protected boolean isEligible(Class<?> targetClass) { //首次從 eligibleBeans 這個 map 中獲取值肯定為 null Boolean eligible = this.eligibleBeans.get(targetClass); if (eligible != null) { return eligible; } //如果沒有設定 advisor(即:切面),返回 false if (this.advisor == null) { return false; } // 若類或方法有 @Aysnc 註解,AopUtils.canApply 會判斷為 true eligible = AopUtils.canApply(this.advisor, targetClass); this.eligibleBeans.put(targetClass, eligible); return eligible; }
有三種方案:
懶載入:使用@Lazy或者@ComponentScan(lazyInit = true)
不要讓@Async的Bean參與迴圈依賴
將allowRawInjectionDespiteWrapping設定為true
說明
建議使用@Lazy。
不建議使用@ComponentScan(lazyInit = true),因為它是全域性的,容易產生誤傷。
範例
這兩個方法都是可以的:
法1:將@Lazy放到A類的b成員上邊
package com.knife.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class A { @Lazy @Autowired private B b; @Async public void print() { System.out.println("Hello World"); } }
法2:將@Lazy放到B類的a成員上邊
package com.knife.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component public class B { @Lazy @Autowired private A a; }
這樣啟動就能成功。
原理分析
以這種寫法為例進行分析:@Lazy放到A類的b成員上邊。
即:
package com.knife.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class A { @Lazy @Autowired private B b; @Async public void print() { System.out.println("Hello World"); } }
假設 A 先載入,在建立 A 的範例時,會觸發依賴屬性 B 的載入,在載入 B 時發現它是一個被 @Lazy 標記過的屬性。那麼就不會去直接載入 B,而是產生一個代理物件注入到了 A 中,這樣 A 就能正常的初始化完成放入一級快取了。
B 載入時,將前邊生成的B代理物件取出,再注入 A 就能直接從一級快取中獲取到 A,這樣 B 也能正常初始化完成了。所以,迴圈依賴的問題就解決了。
略。
說明
本方法不建議使用。
這樣設定後,容器啟動不報錯了。但是:Bean A的@Aysnc方法不起作用了。因為Bean B裡面依賴的a是個原始物件,所以它不能執行非同步操作(即使容器內的a是個代理物件)。
方法
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.stereotype.Component; @Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true); } }
概述
同為建立動態代理物件,同為一個註解標註在類/方法上,為何@Transactional就不會出現這種啟動報錯呢?
原因是,它們代理的建立的方式不同:
@Transactional建立代理的方式:使用自動代理建立器InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator的子類),它實現了getEarlyBeanReference()方法從而很好的對迴圈依賴提供了支援
@Async建立代理的方式:使用AsyncAnnotationBeanPostProcessor單獨的後置處理器。它只在一處postProcessAfterInitialization()實現了對代理物件的建立,因此若它被迴圈依賴了,就會報錯
詳解
處理@Transactional註解的是InfrastructureAdvisorAutoProxyCreator,它是SmartInstantiationAwareBeanPostProcessor的子類。AbstractAutoProxyCreator對SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法進行了覆寫:
AbstractAutoProxyCreator# getEarlyBeanReference
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { // 其他程式碼 @Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } }
AbstractAutoProxyCreator#postProcessAfterInitialization方法中,判斷是否代理過,是的話,直接返回:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { // 其他程式碼 @Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } }
以上就是Spring處理@Async導致的迴圈依賴失敗問題的方案詳解的詳細內容,更多關於Spring 迴圈依賴失敗的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45