首頁 > 軟體

Spring處理@Async導致的迴圈依賴失敗問題的方案詳解

2022-07-09 14:00:41

簡介

說明

本文介紹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;
    }
  1. 建立A,A範例化完成後將自己放入第三級快取,然後給A的依賴屬性b賦值
  2. 建立B,B範例化後給B的依賴屬性a賦值
  3. 從第三級快取中獲得A(執行A的getEarlyBeanReference方法)。執行getEarlyBeanReference()時@Async根本還被掃描,所以返回的是原始型別地址(沒被代理的物件地址)。
  4. B完成初始化、屬性的賦值,此時持有A原始型別參照(沒被代理)
  5. 完成A的屬性的賦值(此時持有B的參照),繼續執行初始化方法initializeBean(...),解析@Aysnc註解,生成一個代理物件,exposedObject是一個代理物件(而非原始物件),加入到容器裡。
  6. 問題出現了:B的屬性A是個原始物件,而此處的範例A卻是個代理物件。(即:B裡的A不是最終物件(不是最終放進容器的物件))
  7. 執行自檢程式:由於allowRawInjectionDespiteWrapping預設值是false,表示不允許上面不一致的情況發生,就報錯了

解決方案

有三種方案:

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

不要讓@Async的Bean參與迴圈依賴

將allowRawInjectionDespiteWrapping設定為true

方案1:懶載入

說明

建議使用@Lazy。

不建議使用@ComponentScan(lazyInit = true),因為它是全域性的,容易產生誤傷。

範例

這兩個方法都是可以的:

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

這樣啟動就能成功。

原理分析

以這種寫法為例進行分析:@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 也能正常初始化完成了。所以,迴圈依賴的問題就解決了。

方案2:不讓@Async的類有迴圈依賴

略。

方案3:allowRawInjectionDespiteWrapping設定為true

說明

本方法不建議使用。

這樣設定後,容器啟動不報錯了。但是: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就不會出現這種啟動報錯呢?

原因是,它們代理的建立的方式不同:

@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其它相關文章!


IT145.com E-mail:sddin#qq.com