欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring處理@Async導(dǎo)致的循環(huán)依賴失敗問(wèn)題的方案詳解

 更新時(shí)間:2022年07月09日 09:56:54   作者:IT利刃出鞘  
這篇文章主要為大家詳細(xì)介紹了SpringBoot中的@Async導(dǎo)致循環(huán)依賴失敗的原因及其解決方案,文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下

簡(jiǎn)介

說(shuō)明

本文介紹SpringBoot中的@Async導(dǎo)致循環(huán)依賴失敗的原因及其解決方案。

概述

我們知道,Spring解決了循環(huán)依賴問(wèn)題,但Spring的異步(@Async)會(huì)使得循環(huán)依賴失敗。本文將用實(shí)例來(lái)介紹其原因和解決方案。

問(wèn)題復(fù)現(xiàn)

啟動(dòng)類

啟動(dòng)類添加@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";
    }
}

啟動(dòng):(報(bào)錯(cuò))

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開啟時(shí)向容器內(nèi)注入AsyncAnnotationBeanPostProcessor,它是一個(gè)BeanPostProcessor,實(shí)現(xiàn)了postProcessAfterInitialization方法。創(chuàng)建代理的動(dòng)作在抽象父類AbstractAdvisingBeanPostProcessor上:

    // 這個(gè)方法主要是為有@Async 注解的 bean 生成代理對(duì)象
    @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已經(jīng)被代理了(比如已經(jīng)被事務(wù)那邊給代理了~~)
        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最先執(zhí)行還是最后執(zhí)行
                // 此處的advisor為:AsyncAnnotationAdvisor  它切入Class和Method標(biāo)注有@Aysnc注解的地方~~~
                if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                } else {
                    advised.addAdvisor(this.advisor);
                }
                return bean;
            }
        }
        // 若不是代理對(duì)象,則進(jìn)行處理
        if (isEligible(bean, beanName)) {
            //copy屬性 proxyFactory.copyFrom(this); 工廠模式生成一個(gè)新的 ProxyFactory
            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
            // 如果沒(méi)有采用CGLIB,就去探測(cè)它的接口
            if (!proxyFactory.isProxyTargetClass()) {
                evaluateProxyInterfaces(bean.getClass(), proxyFactory);
            }
            // 切入切面并創(chuàng)建一個(gè)getProxy 代理對(duì)象
            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 這個(gè) map 中獲取值肯定為 null
        Boolean eligible = this.eligibleBeans.get(targetClass);
        if (eligible != null) {
            return eligible;
        }
        //如果沒(méi)有配置 advisor(即:切面),返回 false
        if (this.advisor == null) {
            return false;
        }
        
        // 若類或方法有 @Aysnc 注解,AopUtils.canApply 會(huì)判斷為 true
        eligible = AopUtils.canApply(this.advisor, targetClass);
        this.eligibleBeans.put(targetClass, eligible);
        return eligible;
    }
  1. 創(chuàng)建A,A實(shí)例化完成后將自己放入第三級(jí)緩存,然后給A的依賴屬性b賦值
  2. 創(chuàng)建B,B實(shí)例化后給B的依賴屬性a賦值
  3. 從第三級(jí)緩存中獲得A(執(zhí)行A的getEarlyBeanReference方法)。執(zhí)行g(shù)etEarlyBeanReference()時(shí)@Async根本還被掃描,所以返回的是原始類型地址(沒(méi)被代理的對(duì)象地址)。
  4. B完成初始化、屬性的賦值,此時(shí)持有A原始類型引用(沒(méi)被代理)
  5. 完成A的屬性的賦值(此時(shí)持有B的引用),繼續(xù)執(zhí)行初始化方法initializeBean(...),解析@Aysnc注解,生成一個(gè)代理對(duì)象,exposedObject是一個(gè)代理對(duì)象(而非原始對(duì)象),加入到容器里。
  6. 問(wèn)題出現(xiàn)了:B的屬性A是個(gè)原始對(duì)象,而此處的實(shí)例A卻是個(gè)代理對(duì)象。(即:B里的A不是最終對(duì)象(不是最終放進(jìn)容器的對(duì)象))
  7. 執(zhí)行自檢程序:由于allowRawInjectionDespiteWrapping默認(rèn)值是false,表示不允許上面不一致的情況發(fā)生,就報(bào)錯(cuò)了

解決方案

有三種方案:

懶加載:使用@Lazy或者@ComponentScan(lazyInit = true)

不要讓@Async的Bean參與循環(huán)依賴

將allowRawInjectionDespiteWrapping設(shè)置為true

方案1:懶加載

說(shuō)明

建議使用@Lazy。

不建議使用@ComponentScan(lazyInit = true),因?yàn)樗侨值?,容易產(chǎn)生誤傷。

實(shí)例

這兩個(gè)方法都是可以的:

  • 法1. 將@Lazy放到A類的b成員上邊
  • 法2: 將@Lazy放到B類的a成員上邊

法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;
}

這樣啟動(dòng)就能成功。

原理分析

以這種寫法為例進(jìn)行分析:@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");
    }
}

假設(shè) A 先加載,在創(chuàng)建 A 的實(shí)例時(shí),會(huì)觸發(fā)依賴屬性 B 的加載,在加載 B 時(shí)發(fā)現(xiàn)它是一個(gè)被 @Lazy 標(biāo)記過(guò)的屬性。那么就不會(huì)去直接加載 B,而是產(chǎn)生一個(gè)代理對(duì)象注入到了 A 中,這樣 A 就能正常的初始化完成放入一級(jí)緩存了。

B 加載時(shí),將前邊生成的B代理對(duì)象取出,再注入 A 就能直接從一級(jí)緩存中獲取到 A,這樣 B 也能正常初始化完成了。所以,循環(huán)依賴的問(wèn)題就解決了。

方案2:不讓@Async的類有循環(huán)依賴

略。

方案3:allowRawInjectionDespiteWrapping設(shè)置為true

說(shuō)明

本方法不建議使用。

這樣配置后,容器啟動(dòng)不報(bào)錯(cuò)了。但是:Bean A的@Aysnc方法不起作用了。因?yàn)锽ean B里面依賴的a是個(gè)原始對(duì)象,所以它不能執(zhí)行異步操作(即使容器內(nèi)的a是個(gè)代理對(duì)象)。

方法

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不會(huì)導(dǎo)致失敗

概述

同為創(chuàng)建動(dòng)態(tài)代理對(duì)象,同為一個(gè)注解標(biāo)注在類/方法上,為何@Transactional就不會(huì)出現(xiàn)這種啟動(dòng)報(bào)錯(cuò)呢?

原因是,它們代理的創(chuàng)建的方式不同:

@Transactional創(chuàng)建代理的方式:使用自動(dòng)代理創(chuàng)建器InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator的子類),它實(shí)現(xiàn)了getEarlyBeanReference()方法從而很好的對(duì)循環(huán)依賴提供了支持

@Async創(chuàng)建代理的方式:使用AsyncAnnotationBeanPostProcessor單獨(dú)的后置處理器。它只在一處postProcessAfterInitialization()實(shí)現(xiàn)了對(duì)代理對(duì)象的創(chuàng)建,因此若它被循環(huán)依賴了,就會(huì)報(bào)錯(cuò)

詳解

處理@Transactional注解的是InfrastructureAdvisorAutoProxyCreator,它是SmartInstantiationAwareBeanPostProcessor的子類。AbstractAutoProxyCreator對(duì)SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法進(jìn)行了覆寫:

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方法中,判斷是否代理過(guò),是的話,直接返回:

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導(dǎo)致的循環(huán)依賴失敗問(wèn)題的方案詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring 循環(huán)依賴失敗的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 簡(jiǎn)單了解Thymeleaf語(yǔ)法 數(shù)據(jù)延遲加載使用實(shí)例

    簡(jiǎn)單了解Thymeleaf語(yǔ)法 數(shù)據(jù)延遲加載使用實(shí)例

    這篇文章主要介紹了簡(jiǎn)單了解Thymeleaf語(yǔ)法 數(shù)據(jù)延遲加載使用實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2010-05-05
  • Java之理解Redis回收算法LRU案例講解

    Java之理解Redis回收算法LRU案例講解

    這篇文章主要介紹了Java之理解Redis回收算法LRU案例講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 利用Java實(shí)現(xiàn)mTLS調(diào)用

    利用Java實(shí)現(xiàn)mTLS調(diào)用

    這篇文章主要介紹使用 Java作為客戶端 與受 mTLS 保護(hù)的服務(wù)交互。為了對(duì)我們的 Java 客戶端進(jìn)行 ssl 配置,我們需要先設(shè)置一個(gè) SSLContext。這簡(jiǎn)化了事情,因?yàn)?SSLContext 可用于各種 http 客戶端,接下來(lái)我們一起進(jìn)入下面文章了解具體內(nèi)容,需要的朋友可以參考一下
    2021-11-11
  • Java主鍵生成之@Id和@GeneratedValue使用詳解

    Java主鍵生成之@Id和@GeneratedValue使用詳解

    這篇文章主要介紹了Java主鍵生成之@Id和@GeneratedValue的使用,@Id和@GeneratedValue注解就是JPA中用于定義主鍵和主鍵生成策略的關(guān)鍵注解,理解這兩個(gè)注解的使用和不同的主鍵生成策略,對(duì)于開發(fā)高效、穩(wěn)定的數(shù)據(jù)持久化應(yīng)用至關(guān)重要,需要的朋友可以參考下
    2025-05-05
  • Java 實(shí)現(xiàn)將List平均分成若干個(gè)集合

    Java 實(shí)現(xiàn)將List平均分成若干個(gè)集合

    這篇文章主要介紹了Java 實(shí)現(xiàn)將List平均分成若干個(gè)集合,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • Java搜索與圖論之DFS和BFS算法詳解

    Java搜索與圖論之DFS和BFS算法詳解

    DFS指在進(jìn)行算法運(yùn)算時(shí),優(yōu)先將該路徑的當(dāng)前路徑執(zhí)行完畢,執(zhí)行完畢或失敗后向上回溯嘗試其他途徑。BFS指在進(jìn)行算法運(yùn)算時(shí),優(yōu)先將當(dāng)前路徑點(diǎn)的所有情況羅列出來(lái),然后根據(jù)羅列出來(lái)的情況羅列下一層。本文介紹了二者的實(shí)現(xiàn)與應(yīng)用,需要的可以參考一下
    2022-11-11
  • Java構(gòu)造函數(shù)與普通函數(shù)用法詳解

    Java構(gòu)造函數(shù)與普通函數(shù)用法詳解

    本篇文章給大家詳細(xì)講述了Java構(gòu)造函數(shù)與普通函數(shù)用法以及相關(guān)知識(shí)點(diǎn),對(duì)此有興趣的朋友可以參考學(xué)習(xí)下。
    2018-03-03
  • Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生管理系統(tǒng)

    Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • java獲得mysql和oracle鏈接的類

    java獲得mysql和oracle鏈接的類

    這篇文章主要介紹了java獲得mysql和oracle鏈接的類,可實(shí)現(xiàn)基于jdbc的mysql與oracle數(shù)據(jù)庫(kù)連接,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • 詳解Java8中的lambda表達(dá)式、::符號(hào)和Optional類

    詳解Java8中的lambda表達(dá)式、::符號(hào)和Optional類

    這篇文章主要介紹了Java8中的lambda表達(dá)式、::符號(hào)和Optional類,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04

最新評(píng)論