(一)循环依赖,你真的懂了吗?万字解析循环依赖底层原理 - 什么是Bean循环依赖?Spring是如何解决的?二级缓存可以解决吗?遇到相关报错如何处理?

一、相关知识点简介

Spring Boot是基于Spring框架的一个快速开发平台,旨在简化Spring应用的创建和部署。通过提供一系列开箱即用的默认配置和自动化工具,Spring Boot使开发者能够专注于业务逻辑,而无需处理复杂的配置和依赖管理。其内嵌的Web服务器、丰富的生态系统和强大的社区支持,使得Spring Boot成为现代Java应用开发的首选框架之一。

在这里插入图片描述

在使用Spring Boot开发过程中,循环依赖是一个常见且复杂的问题。循环依赖指的是两个或多个Bean互相依赖,导致Bean的创建过程陷入死循环。Spring Boot通过三级缓存和代理机制巧妙地解决了这一问题,确保Bean的依赖注入和AOP增强能够顺利进行。本文将深入探讨Spring Boot如何处理循环依赖,帮助开发者更好地理解和应用这一强大的框架。

1.1、什么是Bean循环依赖?

循环依赖是指Bean对象循环引用,是两个或多个Bean之间相互持有对方的引用。循环依赖有2种表现形式:第一种是相互依赖,也就是A依赖B,B又依赖A;

在这里插入图片描述

第二种是自我依赖,也就是A依赖自己形成自我依赖。

在这里插入图片描述

对象引用循环依赖在某些业务场景上可能是合理存在的,但是由于Spring容器设计了依赖注入机制,即Spring容器在创建bean实例化以后就要给bean中的属性自动赋值,要全部自动赋值之后,才能交给用户使用。

如果出现循环依赖的情况,以两个bean互相依赖的情况作为举例,假设有AService已经实例化(但未完成初始化),但是AService中需要自动赋值的BService并没有初始化,如果Spring立刻初始化BService,发现BService中需要自动赋值AService也没有初始化完成,这样就会出现相互等待,形成死循环,可能导致Spring容器都无法启动了。

由此可见,对Bean的填充属性是循环依赖源头的开始。

1.2、Spring创建Bean主要流程

为了容易理解Spring解决循环依赖过程,我们先简单温习下Spring容器创建Bena的主要流程。

从代码看Spring对于Bean的生成过程,步骤还是很多的,我把一些扩展业务代码省略掉,先上点开胃菜:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)        throws BeanCreationException {    
    if (mbd.isSingleton()) {        
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);   
    }    
    // Bean初始化第一步:默认调用无参构造实例化Bean    
    // 如果是只有带参数的构造方法,构造方法里的参数依赖注入,就是发生在这一步    
    if (instanceWrapper == null) {        
        instanceWrapper = createBeanInstance(beanName, mbd, args);    
    }    
    // Initialize the bean instance.    
    Object exposedObject = bean;    
    try {        
        // bean创建第二步:填充属性(DI依赖注入发生在此步骤)        
        populateBean(beanName, mbd, instanceWrapper);        
        // bean创建第三步:调用初始化方法,完成bean的初始化操作(AOP的第三个入口)       // AOP是通过自动代理创建器AbstractAutoProxyCreator的postProcessAfterInitialization()
        //方法的执行进行代理对象的创建的,AbstractAutoProxyCreator是BeanPostProcessor接口的实现        
        exposedObject = initializeBean(beanName, exposedObject, mbd);    
    }    
    catch (Throwable ex) {        // ...    
    }    
    // ...

从上述代码看出,整体脉络可以归纳成3个核心步骤:

1. 实例化Bean

主要是通过反射调用默认构造函数创建Bean实例,此时bean的属性都还是默认值null。被注解@Bean标注的方法就是此阶段被调用的。

2. 填充Bean属性

这一步主要是对bean的依赖属性进行填充,对@Value @Autowired @Resource注解标注的属性注入对象引用。

3. 调用Bean初始化方法

调用配置指定中的init 方法,如xml文件指定bean的init-method方法或注解@Bean(initMethod = "initMethod")指定的方法。

1.3、Bean创建过程BeanPostProcessor接口拓展点

在Bean创建的流程中Spring提供了多个BeanPostProcessor接口(下称BPP)方便开发者对Bean进行自定义调整和加工。有以下几种BPP接口比较常用:

  • postProcessMergedBeanDefinition:可对BeanDefinition添加额外的自定义配置

  • getEarlyBeanReference:返回早期暴露的bean引用,一个典型的例子是循环依赖时如果有动态代理,需要在此先返回代理实例

  • postProcessAfterInstantiation:在populateBean前用户可以手动注入一些属性

  • postProcessProperties:对属性进行注入,例如配置文件加密信息在此解密后注入

  • postProcessBeforeInitialization:属性注入后的一些额外操作

  • postProcessAfterInitialization:实例完成创建的最后一步,这里也是一些BPP进行AOP代理的时机.

最后,对bean的生命流程进行一个流程图的总结

在这里插入图片描述

bean的生命流程图

此处敲黑板划重点:Spring的动态代理(AOP)是通过BPP实现的(在图三中的3.4步实现),其中AbstractAutoProxyCreator是十分典型的自动代理类,它实现了SmartInstantiationAwareBeanPostProcessor接口,并重写了getEarlyBeanReferencepostProcessAfterInitialization两个方法实现代理的逻辑,这样完成对原始Bean进行增强,生成新Bean对象,将增强后的新Bean对象注入到属性依赖中。

二、Spring如何解决循环依赖?

先说结论,Spring是通过三级缓存和提前曝光的机制来解决循环依赖的问题。

2.1、三级缓存作用

三级缓存其实就是用三个Map来存储不同阶段Bean对象。

在这里插入图片描述

一级缓存 singletonObjects: 主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例Bean实例,这样的Bean能够直接提供给用户使用,我们称之为终态Bean或叫成熟Bean。

二级缓存 earlySingletonObjects: 主要存放的已经完成初始化但属性还没自动赋值的Bean,这些Bean还不能提供用户使用,只是用于提前暴露的Bean实例,我们把这样的Bean称之为临时Bean或早期的Bean(半成品Bean)

三级缓存 singletonFactories: 存放的是ObjectFactory的匿名内部类实例,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,该方法可以获取提前暴露的单例bean引用。

2.2、三级缓存解决循环依赖过程

现在通过源码分析,深入理解下Spring如何运用三级缓存解决循环依赖。Spring创建Bean的核心代码doGetBean中,在实例化bean之前,会先尝试从三级缓存获取bean,这也是Spring解决循环依赖的开始。

我们假设现在有这样的场景AService依赖BServiceBService依赖AService

一开始加载AService Bean首先依次从一二三级缓存中查找是否存在beanName=AService的对象。

// AbstractBeanFactory.java    
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,                              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {        
   final String beanName = transformedBeanName(name);        
   // 1.尝试从缓存中获取bean,AService还没创建三级缓存都没命中        
   Object sharedInstance = getSingleton(beanName);        
   if (mbd.isSingleton()) {                          
       sharedInstance = getSingleton(beanName,    () -> {  
       //注意此处参数是一个lambda表达式即参数传入的是ObjectFactory类型一个匿名内部类对象
       try { 
           return createBean(beanName, mbd, args);
       } catch (BeansException ex) {}                                      
       });            
       beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);        
   }    
}

因为AService还没创建三级缓存都没命中于是走到创建Bean代码逻辑。调用方法getSingleton(String beanName,ObjectFactory objectFactory)方法,第二个参数传入一个ObjectFactory接口的匿名内部类实例。

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
    //将当前beanName放到singletonsCurrentlyInCreation 集合中,标识该bean正在创建    
    beforeSingletonCreation(beanName);    
    //通过回调getObject()方法触发AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的执行    
    singletonObject = singletonFactory.getObject();    
    afterSingletonCreation(beanName);    
    addSingleton(beanName, singletonObject);
}

该方法主要做四件事情:

  • 将当前beanName放到singletonsCurrentlyInCreation集合中标识该bean正在创建;
  • 调用匿名内部类实例对象的getObject()方法触发AbstractAutowireCapableBeanFactory#createBean方法的执行;
  • 将当前beanNamesingletonsCurrentlyInCreation集合中移除;

singletonFactory.getObject()方法触发回调AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的执行,走真正创建AService Bean流程。

//真正创建Bean的地方 AbstractAutowireCapableBeanFactory#doCreateBean    
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)  throws BeanCreationException {
    // Instantiate the bean.        
    BeanWrapper instanceWrapper = null;        
    if (mbd.isSingleton()) {            
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);        
    }        
    // bean初始化第一步:默认调用无参构造实例化Bean        
    // 构造参数依赖注入,就是发生在这一步        
    if (instanceWrapper == null) {            
        instanceWrapper = createBeanInstance(beanName, mbd, args);        
    }        
    // 实例化后的Bean对象        
    final Object bean = instanceWrapper.getWrappedInstance();        // 将刚创建的bean放入三级缓存中singleFactories(key是beanName,value是ObjectFactory)        
    //注意此处参数又是一个lambda表达式即参数传入的是ObjectFactory类型一个匿名内部类对象,在后续再缓存中查找Bean时会触发匿名内部类getEarlyBeanReference()方法回调        
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));        
    // Initialize the bean instance.        
    Object exposedObject = bean;        
    try {            
        // bean创建第二步:填充属性(DI依赖注入发生在此步骤)
        populateBean(beanName, mbd, instanceWrapper); 
        // bean创建第三步:调用初始化方法,完成bean的初始化操作(AOP的第三个入口) 
        // AOP是通过自动代理创建器AbstractAutoProxyCreator的postProcessAfterInitialization()
        //方法的执行进行代理对象的创建的,AbstractAutoProxyCreator是BeanPostProcessor接口的实现        
        exposedObject = initializeBean(beanName, exposedObject, mbd);        
    } catch (Throwable ex) {
        // ...
    }
}

在创建AService Bean的过程中,AService实例化后会调用addSingletonFactory(String beanName, ObjectFactory singletonFactory)方法,将以AService为key、一个ObjectFactory匿名内部类对象为value放入三级缓存中。在后续使用AService时,系统会依次在一、二、三级缓存中查找,最终在三级缓存中找到这个匿名内部类对象,并触发其getEarlyBeanReference()方法的回调。

之所以不直接将AService实例放入三级缓存,是因为AOP增强逻辑是在创建Bean的第三步,即调用初始化方法之后进行的。AOP增强后会生成一个新的代理类AServiceProxy实例。如果此时直接把AService实例放入三级缓存,那么在对BService Bean的aService属性进行依赖注入时,注入的将是原始的AService实例,而不是增强后的AServiceProxy实例。

在将以AService为key、ObjectFactory匿名内部类对象为value放入三级缓存后,系统继续对AService进行属性填充(依赖注入),此时发现AService依赖BService。于是,系统依次在一、二、三级缓存中查找BService Bean,由于没有找到,系统按照上述流程实例化BService,并将以BService为key、ObjectFactory匿名内部类对象为value放入三级缓存中。接着,系统继续对BService进行属性填充(依赖注入),这时又发现BService依赖AService,于是系统再次依次在一、二、三级缓存中查找AService

//DefaultSingletonBeanRegistry.java
public Object getSingleton(String beanName) {    
    return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {        
    // 从一级缓存获取,key=AService        
    Object singletonObject = this.singletonObjects.get(beanName); 
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {            
    synchronized (this.singletonObjects) {
        // 从二级缓存获取,key=AService
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 是否允许循环引用
        if (singletonObject == null && allowEarlyReference) { 
        // 前面已经将以Key为AService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存了                    
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);                    
        if (singletonFactory != null) {                         
            //singletonFactory是一个匿名内部类对象,此处触发匿名内部类中getEarlyBeanReference()方法回调。                        
            singletonObject = singletonFactory.getObject(); 
            // 将三级缓存生产的bean放入二级缓存中 
            this.earlySingletonObjects.put(beanName, singletonObject);           
            // 删除三级缓存   
            this.singletonFactories.remove(beanName);
         }                
       }            
     }        
  }      
  return singletonObject;    
}

在Spring处理循环依赖时,三级缓存起到了关键作用。当系统需要AService的实例时,它首先在三级缓存中查找之前存入的以AService为key的ObjectFactory匿名内部类对象。这触发了getEarlyBeanReference()方法的回调,该方法决定返回的是原始的AService实例还是经过AOP增强后的AServiceProxy实例。如果没有AOP切面拦截AService,返回的将是原始的AService实例。随后,系统将这个半成品的AService Bean放入二级缓存,并从三级缓存中删除AService的条目。这个机制巧妙地实现了提前暴露AService Bean,使BService能够完成属性依赖注入。接下来,BService继续其初始化过程,最终生成了完整的BService Bean实例。

BService创建完成后,控制流程返回到AService的创建过程。此时,AService成功获取到了其依赖的BService实例,从而能够完成自身的后续初始化工作。通过这种精心设计的机制,Spring框架优雅地解决了AServiceBService之间的循环依赖问题。这一过程展示了Spring在处理复杂依赖关系时的灵活性和效率,同时确保了在有AOP介入的情况下,依赖注入和面向切面编程能够和谐共存。

最后,来一张解决AService依赖BServiceBService又依赖AService这样循环依赖的流程图对上述Spring代码逻辑进行总结。
在这里插入图片描述

没有AOP的Bean循环依赖解决的流程图

2.3、 当AOP遇到循环依赖

从2.3、Bean创建过程BeanPostProcessor接口拓展点小节,我们知道Bean的AOP动态代理创建时在初始化之后通过回调postProcessAfterInitialization后置处理器进行的,但是出现循环依赖的Bean如果使用了AOP, 那就需要在getEarlyBeanReference()方法创建动态代理,将生成的代理Bean放在二级缓存提前曝光出来, 这样BService的属性AService注入的就是被代理后的AServiceProxy实例对象。

下面以AService依赖BServiceBService依赖AServiceAService被AOP切面拦截的场景进行代码分析循环依赖的Bean使用了AOP如何在getEarlyBeanReference()方法如何提前创建动态代理Bean。

在这里插入图片描述

// 将Aservice添加三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 添加Bservice的aService属性时从三级中找Aservice的ObjectFactory类型一个匿名内部类对象,从而触发匿名内部类getEarlyBeanReference()方法回调,进入创建AService切面代理对象逻辑
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;    
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {        
    //判断后置处理器是否实现了SmartInstantiationAwareBeanPostProcessor接口        
    //调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference        
    for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {   
        exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);        }    
  }    
  return exposedObject;
}

可以看出getEarlyBeanReference()方法判断后置处理器是否实现了SmartInstantiationAwareBeanPostProcessor后置处理器接口。

而我们演示代码通过@EnableAspectJAutoProxy注解导入的AOP核心业务处理AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。

//真正实现了该方法的类就是AbstractAutoProxyCreator
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {     
    @Override    
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        // 先获取beanName,主要是为FactoryBean类型添加&前缀        
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 判断是否已经在earlyProxyReferences集合中,不在则添加进去
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);        
        }        
        // 创建代理对象,如果必要的话        
        return wrapIfNecessary(bean, beanName, cacheKey);    
    }      
    /**     
     * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.     
     * @param bean the raw bean instance     
     * @param beanName the name of the bean     
     * @param cacheKey the cache key for metadata access     
     * @return a proxy wrapping the bean, or the raw bean instance as-is     
    */    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {        
        // 前面先做一些基本的判断        
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {            
            return bean;        
        }        
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {         
            return bean;        
        }        
        // Advice/Pointcut/Advisor/AopInfrastructureBean接口的beanClass不进行代理以及对beanName为aop内的切面名也不进行代理        
        // 此处可查看子类复写的shouldSkip()方法        
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);            
            return bean;        
        }        
        // Create proxy if we have advice.       
        // 查找对代理类相关的advisor对象集合,此处就与point-cut表达式有关了        
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);        
        // 对相应的advisor不为空才采取代理        
        if (specificInterceptors != DO_NOT_PROXY) {            
            this.advisedBeans.put(cacheKey, Boolean.TRUE);            
            // 通过jdk动态代理或者cglib动态代理,产生代理对象,这里传入的是SingletonTargetSource对象喔,对原始bean对象进行了包装
            Object proxy = createProxy(                    
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));            
            // 放入代理类型缓存            
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;        
        }        
        // 放入通知缓存        
        this.advisedBeans.put(cacheKey, Boolean.FALSE);        
        return bean;    
    }
}

在Spring框架中,AOP和循环依赖的处理是一个复杂而巧妙的过程。以AServiceBService的循环依赖为例,Spring首先通过wrapIfNecessary方法检查是否需要为AService创建代理。由于存在切点表达式@Around("execution(* com.example.service.AService.helloA(..))")拦截了AService,Spring决定创建代理。

使用JDK动态代理或CGLIB,Spring生成AServiceProxy对象,包装原始的AService。为解决循环依赖,Spring采用三级缓存策略:将AServiceProxy放入二级缓存,同时从三级缓存移除AServiceObjectFactory。这样,BService在创建过程中可以从二级缓存获取AServiceProxy并注入其aService属性,完成自身初始化。随后,AService继续其初始化过程,注入已创建的BService,执行初始化方法,并再次触发AOP逻辑。

通过这种方式,Spring巧妙地在处理AOP的同时解决了循环依赖问题,既保证了AOP的正确实现,又避免了循环依赖导致的死锁,展现了框架的强大和灵活性。这个过程涉及提前创建代理对象、利用多级缓存策略和合理安排初始化顺序等关键技术,是Spring框架设计巧妙之处的典型体现。

在这里插入图片描述

在Spring处理循环依赖和AOP的过程中,还有一个重要的细节需要注意。当Spring再次处理AService时,会检查AService是否已经存在于earlyProxyReferences集合中。这个检查发生在getEarlyBeanReference()方法中。如果AService已经在这个集合里,就意味着它已经经历过AOP处理,因此不需要重复进行AOP逻辑。这个机制有效地防止了重复代理,确保了AOP的一致性和效率。通过这种方式,Spring成功地解决了AService依赖BService,而BService又依赖AService这种复杂的循环依赖问题,同时还保证了AOP的正确应用。

为了更好地理解这个过程,我画了一张流程图展示AServiceBService相互依赖,且AService使用了AOP的情况下,Spring如何解决循环依赖。这个流程图的红色部分突出显示与没有AOP情况下的主要区别。

在这里插入图片描述

使用AOP且出现循环依赖的解决流程图

2.4 为什么需要三级缓存,二级缓存不能解决吗?

综上从我们自己去尝试解决循环依赖,学习了循环依赖的核心解决原理。又分析了 Spring 解决的循环依赖的处理过程以及核心源码的分析。那么接下来我们在总结下三级缓存分别不同的处理过程,算是一个总结,也方便大家理解。

1. 一级缓存能解决吗?
在这里插入图片描述

  • 其实只有一级缓存并不是不能解决循环依赖,就像我们自己做的例子一样。
  • 但是在 Spring 中如果像我们例子里那么处理,就会变得非常麻烦,而且也可能会出现 NPE 问题。
  • 所以如图按照 Spring 中代码处理的流程,我们去分析一级缓存这样存放成品 Bean 的流程中,是不能解决循环依赖的问题的。因为 A 的成品创建依赖于 B,B的成品创建又依赖于 A,当需要补全B的属性时 A 还是没有创建完,所以会出现死循环。

2. 二级缓存能解决吗?
在这里插入图片描述

  • 有了二级缓存其实这个事处理起来就容易了,一个缓存用于存放成品对象,另外一个缓存用于存放半成品对象。
  • A 在创建半成品对象后存放到缓存中,接下来补充 A 对象中依赖 B 的属性。
  • B 继续创建,创建的半成品同样放到缓存中,在补充对象的 A 属性时,可以从半成品缓存中获取,现在 B 就是一个完整对象了,而接下来像是递归操作一样 A 也是一个完整对象了。

3. 三级缓存解决什么?
在这里插入图片描述

  • 有了二级缓存都能解决 Spring 依赖了,怎么要有三级缓存呢。其实我们在前面分析源码时也提到过,三级缓存主要是解决 Spring AOP 的特性。AOP 本身就是对方法的增强,是 ObjectFactory<?> 类型的 lambda 表达式,而 Spring 的原则又不希望将此类类型的 Bean 前置创建,所以要存放到三级缓存中处理。
  • 其实整体处理过程类似,唯独是 B 在填充属性 A 时,先查询成品缓存、再查半成品缓存,最后在看看有没有单例工程类在三级缓存中。最终获取到以后调用 getObject 方法返回代理引用或者原始引用。
  • 至此也就解决了 Spring AOP 所带来的三级缓存问题。

三、如何解决循环依赖报错

1.重新设计

当有一个循环依赖,很可能是有一个设计问题并且各责任没有得到很好的分离。应该尽量正确地重新设计组件,以便它们的层次是精心设计的,也没有必要循环依赖。

如果不能重新设计组件(可能有很多的原因:遗留代码,已经被测试并不能修改代码,没有足够的时间或资源来完全重新设计…),但有一些变通方法来解决这个问题。

2.@Lazy

解决Spring 循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。

@Service
public class ServiceAImpl implements ServiceA {
    private ServiceB serviceB;
    public ServiceAImpl(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

3.Setter/Field 注入

简单地说,你对你需要注入的bean是使用setter注入(或字段注入),而不是构造函数注入。通过这种方式创建Bean,实际上它此时的依赖并没有被注入,只有在你须要的时候他才会被注入进来。

@Service
public class ServiceAImpl implements ServiceA {
    private ServiceB serviceB;
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

SpringBoot 2.6.x不推荐使用循环依赖,最简单的方式是在全局配置文件中允许循环引用存在,此属性默认值为false,显示声明为true,可回避项目启动时控制台循环引用异常。

spring:
  main:
    allow-circular-references: true

4.@PostConstruct

打破循环的另一种方式是:在要注入的属性(该属性是一个bean)上使用 @Autowired ,并使用@PostConstruct 标注在另一个方法,且该方法里设置对其他的依赖。

@Service
public class ServiceAImpl implements ServiceA {
    @Autowired
    private ServiceB serviceB;
    @PostConstruct
    public void init() {
        System.out.println(serviceB);
        serviceB.setServiceA(this);
    }
}
@Service
public class ServiceBImpl implements ServiceB {
    private ServiceA serviceA;
    public void setServiceA(ServiceA serviceA) {
        System.out.println(serviceA);
        this.serviceA = serviceA;
    }
}

5.总结

方式依赖情况注入方式能够解决循环依赖
情况一AB相互依赖均采用setter方式
情况二AB相互依赖均采用构造器方式不能
情况三AB相互依赖A中注入B采用setter,B中注入A采用构造器
情况四AB相互依赖A中注入B采用构造器,B中注入A采用setter不能
情况五AB相互依赖A中注入B采用@Autowired,B中注入A采用@PostConstruct + setter
情况六AB相互依赖A中注入B采用@PostConstruct + setter,B中注入A采用@Autowired

四、一次循环依赖报错引发的思考

有好几次线上发布老应用时,遭遇代码启动报错,具体错误如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'xxxManageFacadeImpl': Bean with name 'xxxManageFacadeImpl'  has been injected into other beans [xxxProductMaintenceFacadeImpl] in its raw version as part of a circular reference, but has eventually been wrap means thff, for expped. 
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 'getBeanNamesallowEageOfType' with the 'allowEagerInit' flag turned off

眨眼一看,这不就是Spring Bean循环依赖报错吗?脑海立马闪过那些年为了进京东面试时被死亡N连问的场景,那时我们都知道Spring已经支持bean循环依赖,为啥我们的Springboot应用启动时还报这个错误?

前面章节已经详细讲了Spring通过三级缓存和提前曝光机制解决循环依赖问题。那我们的应用怎么还报此类错误呢?首先回顾下报错详情:

在这里插入图片描述

从错误描述看xxxProductMaintenanceFacadeImpl注入的xxxManageFacadeImpl对象与最终的xxxManageFacadeImpl对象不一致。

从上面代码分析,我们知道Spring能改变单例Bean的对象只有在AOP情况下出现,而出现循环依赖且使用AOP的Bean有getEarlyBeanReference()方法和bean初始化步骤里后置处理器postProcessAfterInitialization两处时机进行AOP,如图五中第18步和第22步。如果是同一个AOP的织入类,那么在bean初始化步骤里后置处理器postProcessAfterInitialization处会判断Bean已经被代理过,不会再做AOP代理。

但现在报错xxxManageFacadeImpl对象最终版本不一致,说明XxxManageFacadeImpl存在另一个AOP的织入类且是在后置处理器postProcessAfterInitialization处进行AOP的。

以下模拟我们的项目代码:

在这里插入图片描述

从示例代码看出AServiceImpl类被@Aspect@Async两个切面注解拦截。

@Aspect注解的AOP核心业务处理由AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。

@Async注解的AOP核心业务处理由AsyncAnnotationBeanPostProcessor类,它只实现了postProcessAfterInitialization()方法,至于为什么@Async不实现提早暴露getEarlyBeanReference(),我还没有想明白。这样@Async注解是在AService初始化步骤里后置处理器postProcessAfterInitialization进行AOP,新生成了AServiceProxy2对象。如下图所示@Aspect注解的AOP是在第18步实现的,这样二级缓存里的存放和BService对象的AService属性注入都是AServiceProxy实例对象;而@Async注解的AOP是在第22步实现的,这是新生成AServiceProxy2实例对象;下图中蓝色部分就是进行两次AOP地方。那么单例Bean AService存在两个AOP后的实例对象,这就违背单例的单一性原则,因此报错了;

在这里插入图片描述

两个AOP代理时机不同导致生成两个代理Bean实例对象

或许到此你还会疑问,这个循环依赖问题为什么日常或预发没出现,而都是线上部署时才遇到报错此错误?

这就跟Spring的Bean加载顺序有关系了, Spring容器载入bean顺序是不确定的,Spring框架没有约定特定顺序逻辑规范。在某些机器环境下是AServiceBService先加载,但在某些环境下是BServiceAService先加载。

还是拿上面示例分析,AServiceImpl类被@Aspect@Async两个切面注解拦截,但是先加载BService再加载AService

在这里插入图片描述

两个AOP代理时机不同导致生成两个代理Bean实例对象

由图可以看出AService的@Aspect和@Async两个注解AOP在都是在后置处理器进行,因此只生成一个代理对象AServiceProxy实例,这种情况下应用启动就不会报错。

四、总结

总结下Spring解决循环依赖的思路:

在创建单例bean时,会把该bean的工厂函数的匿名类对象放入三级缓存中的singletonFactories中;

然后在填充属性时,如果出现循环依赖依赖本 bean,必然执行之前放入的工厂函数的匿名实现,如果该bean无需 AOP的话,工厂函数返回的就是原bean对象;如果该bean有 AOP 的话,也有可能是被某些BBP处理AOP 之后的代理对象,会放入二级缓存中的earlySingletonObjects中;

接着bean开始初始化,如果该bean无需 AOP的话,结果返回的原来创建的bean对象;如果该bean有 AOP 的话,检查AOP织入逻辑是否已经在提前曝光时已经执行了,如果已经执行AOP则返回提前曝光的代理bean对象;如果AOP织入逻辑未执行过,则进行后续的BeanPostProcessor后置处理器进行AOP织入,生成AOP代理bean对象,并返回。

最后对于提前曝光的单例,就会去检查初始化后的bean对象与二级缓存中提前曝光的bean是不是同一个对象,只有不是的情况下才可能抛出异常。

深入阅读我们应用本身代码,发现项目中出现Bean的循环依赖,本质原因是代码架构设计不合理,某些facade类实现了本应在serivce层的业务逻辑,导致其他业务依赖地方反复应用facade层对象。

SpringBoot 2.6.x以上的版本官方已经不推荐使用循环依赖,说不定今后某个最新版本的Spring会强制不能出现Bean循环依赖,因此需要我们开发者在平时编码时要重视代码架构设计。


在这里插入图片描述

感谢大家的观看!!!创作不易,如果觉得我写的好的话麻烦点点赞👍支持一下,谢谢!!!
相关文章已在掘金发布,体验更佳!!

  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值