likes
comments
collection
share

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

作者站长头像
站长
· 阅读数 27

一 前言

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

系统上线时,出现了循环依赖的问题

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

二 循环依赖处理

赶紧看看异常信息:

024-07-30 20:21:17.111-ERROR [] (
rain]
: Application run failed
rg.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'asyncService' : Bean with name
asyncService' has been injected into other beans (recyclerBidCommodityserviceImpl,bizvivoOfflineDubboserviceImpl,
idDiXinTongDubboserviceImpl,bizDixinTongServiceImpl] in its raw version ass part of a circular reference, but has eventually been
rapped. Thismeans that said other beans do not use the final version of the bean. This is often the result of over-eager type
atching - consider using 'getBearNamesoftype' with the 'allomfagerinit' flturned off,for example.
ava:622) ~[spring-beans-5.1.7.RELEASE.jarl/:5.1.7.RELEASE]
at org.springframemork.beans.factory.support.AbstractAutomireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory
at org.springframework.beans.factory.support.AbstractAutomirecapableBeanfactory.createßean(AbstractAutowireCapablegeanfactory.
ava:515) =[spring-beans-5.1.7.RELEASE.jarl/:5.1.7.RELEASE]
spring-beans-5.1.7.RELEASE.jar!/:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanfactory.TambdasdoGetBean$0(AbstractBeanfactory.java:320)
at org.springframework.beans.factory.support.DefaultsingletonBeanmRegistry.getsingleton Default5ingletonBeanRegistry.java:222) 
spring beans-5.1.7.RELEASE.jar!/:5.1.7.RELEASE
at org.springframework.beans.factory.support.AbstractBeanfactory.doGetBean(AbstractBeanfactory.java:318) m[spring-beans-5.1.7
ELEASE.jar!/:5.1.7.RELEASE]
at org.springframmaork.beans.factory. support. AbstractBeanFactory.getBeanbstractBeanfactory.java:199) = spring-beans-5.1.7.
ELEASE.jar!/:5.1.7.RELEASE]
at org.springframework.beans.factory.support.DefaulttistableBeanfaactory.preinstantiateSingletons(DefaultistableBeanFactory
ava:843) m[spring-beans-5.1.7. RELEASE.Jar!/:5.1.7.RELEASE]
at org.springframework.context.support.AbstractApplicationcontext.finishBeeanfactoryInitialization(AbstractApplicationContext
ava:877) ~(spring-context-5.1.7.RELEASE.jar!/:5.1.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh AbstractApplicationContext. java:549)

循环依赖出现的原因是spring在启动容器时,bean互相引用导致

第一步,我们看一下异常信息及对应的类

Error creating bean with name 'asyncService' : Bean with name asyncService' has been injected into other beans (recyclerBidCommodityserviceImpl,bizvivoOfflineDubboserviceImpl, idDiXinTongDubboserviceImpl,bizDixinTongServiceImpl]

异常信息指出,在spring创建asyncService实例时出现了问题,可能是由于他依赖了未被创建的bean,我们再看看涉及的类 asyncService recyclerBidCommodityserviceImpl bizvivoOfflineDubboserviceImpl bidDiXinTongDubboserviceImpl bizDixinTongServiceImpl

第二步,我们看下他们的代码是如何互相引用的,首先看看asyncService


@Service
@Slf4j
public class AsyncService {

@Autowired
private BizZhuanzService zhuanzService;

@Reference(version = "1.0.0", timeout = 3000)
private OfflineVivoPlatformRequestDubboService vivoPlatformRequestDubboService;

@Reference(version = "1.0.0")
private OfflineOrderThirdMappingDubboService offline0rderThirdMappingDubboService;



@Async("bidPool")
@Retryable(value = {Exception.class}, maxAttempts = 5, baackoff = @Backoff(delay = 100, multiplier = 2))
public void sendResult2VivoOfflinePlatform(String orderNop, int bidStatus, Integer bidAmount, String rejectJson)

        OfflineOrderThirdMappingDTO thirdorderDto = offlineOrderThirdMappingDubboService.getByOrderNum(orderNo)
    if (thirdOrderDto == null || StringUtils.isEmpty(thirdoroderDto.getThirdOrderNum()))
        log.error("sendResult2VivoOfflinePlatform,{}找不到第三方订单号",orderNo)
    return;
}

我们看到asyncService引用了BizZhuanzService,接下来看看BizZhuanzService的引用关系

@Component
@Slf4j
public class BizzhuanzServiceImpl implements BizzhuanzService {

private static final int MILLIESECOND_2MIN = 2 * 60* 1000;
@Reference(version = "1.0.0")
private OfflineOrderDubboService offlineOrderDubboService

@Reference(version = "1.0.0")
private OfflineOrderThirdMappingDubboService offlineOrderThirdMappingDubboService;
@Reference(version = "1.0.0")
private OfflineOrderWarehouseConfDubboService orderWarehouseConfDubboService

@Reference(version = "1.0.0")
private AreaDubboService areaDubboService;

诶,也没互相引用呀,咋回事呢,循环依赖不是这样吗?

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

原来除了这种循环依赖还有一种三方互相依赖

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看 也就是BizZhuanzService的引入RecyclerBidCommodityDubboService注入的bean引入了asyncService,那是不是这样呢,我们看一下,发现并没有引入asyncService,就是也不是A引入B,B引入C的情况,那么会不会是C引入D,D引入A呢,我们继续向下看,发现BizZhuanzService引入了RecyclerBidCommodityDubboService而他引入了bizvivoOfflineDubboserviceImpl bidDiXinTongDubboserviceImpl bizDixinTongServiceImpl,而这三个类又引用了asyncService,这就解释通了,我们这次的循环依赖的异常信息中存在asyncService recyclerBidCommodityserviceImpl bizvivoOfflineDubboserviceImpl bidDiXinTongDubboserviceImpl bizDixinTongServiceImpl,这次的依赖关系为

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看 但是这里面会有一个疑问,就是spring其实会帮助我们解决循环依赖的,那这次spring为什么没有成功呢,带着这疑问看看spring解决循环依赖的方式, 首先spirng解决循环依赖其实是使用他的缓存,主要为二级缓存(earlySingletonObjects)和三级缓存(singletonFactories),二级缓存其实就是当实例化bean时,先把bean放入缓存里面,这时的bean并非是已经实例化好的bean,而是一个半成品,放到缓存中的只是bean对象的内存地址

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

我们验证一下,spring是否可以帮助我们处理

@Service
public class AService {
    @Autowired
    BService bService;
    
    void test(){
        System.out.println("test");
    }
}


@Service
public class BService {
    @Autowired
    AService aService;
}

我们启动下服务看下结果

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

服务启动成功说明spring处理了循环依赖,看来的确使用了缓存,那么二级缓存是存储bean对象的引用地址来解决循环依赖,为什么还需要三级缓存呢,三级缓存解决什么问题呢?

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

瞅瞅三级缓存(singletonFactories)

  • 三级缓存。在一级缓存和二级缓存中,缓存的 key 是 beanName,缓存的 value 则是一个 Bean 对象,但是在三级缓存中,缓存的 value 是一个 Lambda 表达式,通过这个 Lambda 表达式可以创建出来目标对象的一个代理对象。

听到代理对象,就会想到Spring耳熟能详的词汇AOP,没错,三级缓存处理的就是AOP产生的代理对象循环依赖问题, 这里我们要先看一下Spring容器的加载流程

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

这里我们会发现,bean在实例化后还会进行一系列流程,而这些流程会对bean进行修改,这些修改就包括我们提到的aop,而谁去进行aop处理呢,他就是BeanPostProcessor

  • BeanPostProcessor 接口用于在 Bean 实例化后对 Bean 进行增强或修改。它可以在 Bean 的初始化过程中对 Bean 进行后处理,例如对 Bean 进行代理、添加额外的功能等。BeanPostProcessor 在 Bean 实例化完成后执行,用于对 Bean 实例进行后处理。

我们前面说,二级缓存实际上存储的是bean的半成品,也就是对象的引用地址,如果没有aop这其实是没有问题,但是aop会创建代理对象,当存在AB互相引用的时候,A获取的是缓存中B对象的内存地址对应的对象,但是后续生成的却是代理对象,代理对象的地址与原来对象的地址是不同的,这就意味这缓存中的对象地址是不正确的,导致出现异常情况,那sping是如何处理的呢

当我们创建一个 AService 的时候,通过反射刚把原始的 AService 创建出来之后,先去判断当前一级缓存中是否存在当前 Bean,如果不存在,则:

  1. 首先向三级缓存中添加一条记录,记录的 key 就是当前 Bean 的 beanName,value 则是一个 Lambda 表达式 ObjectFactory,通过执行这个 Lambda 可以给当前 AService 生成代理对象。
  2. 然后如果二级缓存中存在当前 AService Bean,则移除掉。

现在继续去给 AService 各个属性赋值,结果发现 AService 需要 BService,然后就去创建 BService,创建 BService 的时候,发现 BService 又需要用到 AService,于是就先去一级缓存中查找是否有 AService,如果有,就使用,如果没有,则去二级缓存中查找是否有 AService,如果有,就使用,如果没有,则去三级缓存中找出来那个 ObjectFactory,然后执行这里的 getObject 方法,这个方法在执行的过程中,会去判断是否需要生成一个代理对象,如果需要就生成代理对象返回,如果不需要生成代理对象,则将原始对象返回即可。最后,把拿到手的对象存入到二级缓存中以备下次使用,同时删除掉三级缓存中对应的数据。这样 AService 所依赖的 BService 就创建好了。

接下来继续去完善 AService,去执行各种后置的处理器,此时,有的后置处理器想给 AService 生成代理对象,发现 AService 已经是代理对象了,就不用生成了,直接用已有的代理对象去代替 AService 即可。singletonFactories将创建代理对象提前了。

不得不说spring对循环依赖的处理还是有一套的,那么怎么还出现了我们开通的问题呢,原因就是spring的三级缓存并不能处理所有的aop场景,这个场景就是@Async, 我们先模拟一下使用@Async的场景

@Service
public class AService {
    @Autowired
    BService bService;
    @Async
    void test(){
        System.out.println("test");
    }
}

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

容器启动异常,这样认证了spring的确无法解决@Async,同时也说明了文章开头的问题原因为循环依赖+ @Async 那spring三级缓存为什么处理不了@Async呢?

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看

这就要看下三级缓存的实现原理了,一般的 AOP 都是由 AbstractAutoProxyCreator 这个后置处理器来处理的,通过这个后置处理器生成代理对象,AbstractAutoProxyCreator 后置处理器是 SmartInstantiationAwareBeanPostProcessor 接口的子类,并且 AbstractAutoProxyCreator 后置处理器重写了 SmartInstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法,三级缓存里面存储的代理对象就是从getEarlyBeanReference 方法中获取的。 而 @Async 是由 AsyncAnnotationBeanPostProcessor 来生成代理对象的,AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子类,但是却没有重写 getEarlyBeanReference 方法,默认情况下,getEarlyBeanReference 方法就是将传进来的 Bean 原封不动的返回去,这种情况就意味这三级缓存中并没有存储代理对象,存储了原封不动的bean。

了解了其中的原理,那我们要怎么解决和避免循环依赖呢?

对于文章开头的循环依赖问题,我的解决方案是,阻断循环依赖,也就是asyncService不引用了BizZhuanzService,将原来需要引用BizZhuanzService中的方法,转移到asyncService中实现,问题得到解决,但是这么处理有一个前提,就是被引用的类中的方法要足够简单且便于迁移,对于复杂方法,迁移往往会耗费大量时间及验证。

处理循环依赖还有一些其他方法,而其中较为通用的就是@Lazy

@Service
public class AService {
    @Autowired
    BService bService;
    @Async
    void test(){
        System.out.println("test");
    }
}

@Service
public class BService {
    @Autowired
    @Lazy
    AService aService;
}

被Spring循环依赖硬控两小时后梳理出的解决方案一 前言 系统上线时,出现了循环依赖的问题 二 循环依赖处理 赶紧看看 程序正常启动了!!!

转载自:https://juejin.cn/post/7402372208053665802
评论
请登录