Spring循环依赖

写在前面

        在了解循环依赖之前,要先对以下几点先有个概念

Spring Bean的生命周期(大概)

Spirng AOP大致原理

        一段代码解释AOP的实现原理

public class UserService {
    public void test() {
        // TODO    
    }
}

        生成的代理类为(假设使用cglib)

public class UserProxy extends UserService {
    private UserService target;
    
    public void test() {
        // TODO before
        target.test();
        // TODO after
    }
}

        所以,Spring AOP生成的代理类,其实是持有了原生对象的一个引用,在调用代理对象的方法时,中间会插入调用原对象的方法(不是我们想象中的,直接通过super来调用原生对象的方法)

        ps:这里是否能想通事务失效的问题了?(事务失效问题:在一个类的方法调自己的内部方法A,方法A的事务注解失效)

        原因就是内部调用的时候,用的是原生对象调而不是代理对象,注解自然就失效了

@Lazy注解(懒加载)大致工作原理

  1. 被@Lazy标记的属性,在populateBean注入依赖时,会直接注入一个 proxy 对象,并不是原生对象本身
  2. 在真正使用这个bean的时候,触发getBean时,才会生成真正的对象,执行代码逻辑

什么是循环依赖

        很简单,就是A对象依赖了B对象,B对象依赖了A对象。

// A依赖了B
class A{
    public B b;
}

// B依赖了A
class B{
    public A a;
}

        对应到Spring,循环依赖就是

// A依赖了B
@Component
class A{
    @Autowired
    public B b;
}

// B依赖了A
@Component
class B{
    @Autowired
    public A a;
}

Spring循环依赖会产生什么问题

        那么循环依赖是个问题吗?如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。比如

A a = new A();
B b = new B();
a.b = b;
b.a = a;

        这样,A,B就依赖上了。

        但是,在Spring中循环依赖就是一个问题了,为什么? 因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。

        那我们就来看看Spring循环依赖是怎样产生问题的。
        1. Spring容器启动,扫描代码,拿到A类,需要创建单例放入IOC容器(注意:Spring是严格按照字母的顺序初始化实例的)
        2. 拿到A类后,发现了依赖B,Spring会先到IOC容器查找,B是否已存在,如果存在,则把B注入,如果不存在,则创建B的bean
        3. 创建B的bean,会发现有A的依赖,就会重复2的流程——Spring会先到IOC容器查找,A是否已存在,但是,这时候,A在IOC容器是不存在的,又要去创建A

        至此,循环依赖的问题已经产生

解决循环依赖思路分析

        先来分析为什么缓存能解决循环依赖。

        上文分析得到,之所以产生循环依赖的问题,主要是:A创建时--->需要B---->B去创建--->需要A,从而产生了循环。

 

        那么如何打破这个循环?其实加个中间人(缓存)就可以了。

 

        A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入。

        此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean)。

        B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。

如何解决AOP代理对象替换原生对象进入IOC容器的问题

提前AOP,解决原始对象和代理对象不一致的问题

        看到这里,循环依赖似乎已经解决了。事实上,确实是已经解决了一大半了。

        但是,思考一个问题:如果B依赖的A做了AOP的动作,会不会有问题?

        我们这里可以看到,B注入的A对象,其实是A的原生对象。如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。(前面已经对AOP做了大概介绍)

        B依赖的A和最终的A不是同一个对象!!!这是个很严重的问题,意味这A不是单例的了,而且A的AOP功能在B对象这里全部失效。

        那么如何解决这个问题?这个问题可以说没有办法解决。因为在一个Bean的生命周期最后,Spring提供了BeanPostProcessor可以去对Bean进行加工,这个加工不仅仅只是能修改Bean的属性值,也可以替换掉当前Bean。

        既然无法解决这个问题,那我们就绕过。Spring采取的做法是:提前AOP,把中间缓存的A原始对象,替换成AOP之后的代理对象。这样一改,赋值给B的bean的对象就是A的代理对象而不是A的原始对象了。

singletonsCurrentlyInCreation解决判断循环依赖时机的问题

        有一个问题,我们要知道,不是所有的bean都需要AOP的,假设A没有进行AOP的情况下,你总不能强行让A的代理对象来替代A的原始对象吧(虽然逻辑上是行得通,但是没必要这么做,这样做会打破Spring的生命周期机制)。

        所以,我们必须要有一个机制——A只有出现循环依赖的时候,才提前进行AOP;没有出现循环依赖的时候,还是正常按照Spring生命周期的步骤进行。那我们怎样才能感知到A出现了循环依赖呢?

        其实在每个Bean创建开始的时候,都会有个Set(Set singletonsCurrentlyInCreation)记录着这个Bean是正在创建的过程中的(创建完成后会remove掉这个bean),也就是说,A在进行生命周期的时候,会在这个Set里记录着A正在创建。这个时候B进行生命周期了,要依赖注入A,然而A在单例池里没有,并且在Set里存在,这个时候,我们就知道,AB之间已经产生循环依赖了。

第三级缓存解决循环依赖中出现AOP问题

        基于上面的情况,如果A出现了循环依赖,我们得到结论:

        a. 如果A对象需要进行AOP,我们就返回代理对象

        b. 如果A对象不需要进行AOP,我们就返回原始对象

        所以,这里得出一个结论:第三级缓存必须是一个逻辑表达式而不是一个具体的对象,这个逻辑表达式里面的逻辑就是:如果A出现了AOP,就返回代理对象;如果没有AOP,则返回原始对象。我们看看Spring是怎么做的

// 循环依赖-添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        // 这里可以认为已经出现了AOP的情况
      for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
          // 出现了AOP,返回代理对象
         exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
      }
   }
   return exposedObject;
}

        当获取第三级缓存(singletonFactories)的时候,就会执行Lambda表达式,根据表达式里面的内容,返回对应的对象

多重循环依赖的单例问题

        上文提到,通过第三级缓存,已经解决了循环依赖的对象出现AOP的问题。那还有其他问题吗?我们来看下面一个场景

// A依赖了B并且依赖了C
@Component
class A{
    @Autowired
    public B b;
    @Autowired
    public C c;
}

// B依赖了A
@Component
class B{
    @Autowired
    public A a;
}

// C依赖了A
@Component
class C{
    @Autowired
    public A a;
}

        简单分析一下:

        a. A和B互相依赖,到B的生命周期的时候,发现A在循环依赖,此时单例池里没有A,所以会在第三级缓存里创建A,并且赋值给B的a属性;

        b. A和C互相依赖,到C的生命周期的时候,也发现A在循环依赖,此时单例池里也没有A,又会在第三级缓存里创建A,并且赋值给C的a属性。

        这里,问题就产生了:A是一个单例bean,怎么能同时出现2个实例呢?

二级缓存解决多重循环依赖的单例问题

        其实,上面这个问题出现的原因很简单:2次创建的对象不是同一个。那要解决这个问题,思路就很简单了——我把第一个创建好的对象缓存起来就好了啊,后面继续创建对象的时候,就先从这个缓存里拿,拿不到再去创建。

        事实上,Spring确实也是这么解决的,这就是我们Spring里的第二级缓存——earlySingletonObjects。

        这个时候得来理解一下earlySingletonObjects的作用。此时,我们只得到了A原始对象的代理对象,这个对象还不完整,因为A原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入singletonObjects中,所以只能把代理对象放入earlySingletonObjects,假设现在有其他对象依赖了A,那么则可以从earlySingletonObjects中得到A原始对象的代理对象了,并且是A的同一个代理对象。

循环依赖解决方案总结

        以上面多重循环依赖为例,分析Spring解决循环依赖的整个过程
        1.A的生命周期开始,实例化A
        2.实例化A后,以A的beanName为维度,把A放进三级缓存(缓存里放的其实是一个逻辑表达式)
        3.初始化A,发现依赖了B,进入到B的生命周期
        4.实例化B,B进入到三级缓存,并且初始化B,发现依赖了A
        5.先从单例池(第一级缓存)中找A,没找到;再到第二级缓存里找A,也没找到;再到第三级缓存找A,找到了
        6.根据第三级缓存里A的生成逻辑(即有AOP的情况下生成代理对象,没AOP的情况下生成原始对象),返回实例,然后把实例放到二级缓存,并且删除三级缓存A的实例(因为二级缓存里有A,肯定就不会到三级缓存里找了)
        7.执行B的其他生命周期,直到结束
        8.A又依赖了C,进入到C的生命周期
        9.前面的步骤和A/B类似,最后发现依赖了A
        10.先从单例池(第一级缓存)中找A,没找到;再到第二级缓存里找A,找到了,返回实例
        11.执行C的其他生命周期,直到结束
        12.执行A的其他生命周期,直到结束
        至此,A/B/C的全部生命周期都正常结束了

spring的源代码分析

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // 先从单例池(第一级缓存)里获取数据
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       // 单例池里没有,从第二级缓存获取数据
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            // double check
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // double check
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                   // 第二级缓存也获取不到,从第三级缓存获取数据
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                     // 把第三级缓存生成的对象放进第二级缓存
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     // 删除第三级缓存的数据
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

三级缓存到底是什么

singletonObjects

        单例池,缓存经过了完整生命周期的bean

earlySingletonObjects

        缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入earlySingletonObjects。但是不管怎么样,就算是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects我们就可以统一认为是未经过完整生命周期的bean。

singletonFactories

        缓存的是一个ObjectFactory,也就是一个Lambda表达式。

        在每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中。这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用。

        当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中。如果当前bean在依赖注入时发现出现了循环依赖(当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行Lambda表达式得到一个对象,并把得到的对象放入二级缓存(如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象)。

        其实还有一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。如果进行过AOP了,后续的生命周期就不用再做AOP操作了,反之,则正常按照生命周期进行AOP。

第三级缓存singletonFactories真的有必要存在吗

        思考一个问题——假设没有第三级缓存singletonFactories,只有第二级缓存earlySingletonObjects和单例池,能否满足我们的需求?

        细想一下,好像确实是可以——把生命周期里AOP这个步骤前置不就行了吗?如果生命周期里把AOP前置,我们就无所谓说是拿到的是原生对象还是代理对象了(因为后续不会再进行AOP了,不会存在因为AOP导致对象变化的问题)。

        也许是Spring考虑到这样的改动代价太大,已经改动到生命周期了。Spring不愿意打破生命周期这个机制,因为循环依赖的概率不大,而且引入第三级的缓存代价不大(启动完成以后第二级和第三级缓存都销毁了),所以并没有采取修改生命周期的做法,而是采用三级缓存解决。

        所以,如果没有第三级缓存,其实也是能解决循环依赖这个问题的,只是解决的方式代价有点大。综合起来看,不如采取三级缓存来解决循环依赖。

原型Bean(prototype)的循环依赖问题

看下面一段代码

// A依赖了B
@Component
@Scope("prototype")
class A{
    @Autowired
    public B b;
}

// B依赖了A
@Component
@Scope("prototype")
class B{
    @Autowired
    public A a;
}

        我们来看看,原型的bean出现循环依赖,能不能用我们的三级缓存来解决:

        1.A进行生命周期,实例化A
        2.A依赖了B,进入到B的生命周期,B进行实例化
        3.B又依赖了A,而且A又是个原型的bean,所以又要再创建一个A的bean进行注入
        4.创建A的bean时,又遇到了跟B一样的问题,一直无穷无尽了

        所以,三级缓存是解决不了原型Bean的循环依赖的。看看执行结果

八月 03, 2022 8:40:50 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5197848c: startup date [Wed Aug 03 08:40:50 CST 2022]; root of context hierarchy
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1268)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1080)
	at com.bill.spring.Main.main(Main.java:11)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1268)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
	... 9 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
	... 20 more

Process finished with exit code 1

        怎么办?有个取巧的方式,就是把依赖上加个@Lazy注解,标记为懒加载bean。如:

        

// A依赖了B
@Component
@Scope("prototype")
class A{
    @Lazy
    @Autowired
    public B b;
}

// B依赖了A
@Component
@Scope("prototype")
class B{
    @Lazy
    @Autowired
    public A a;
}

        执行结果

八月 03, 2022 8:42:11 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5197848c: startup date [Wed Aug 03 08:42:11 CST 2022]; root of context hierarchy

Process finished with exit code 0

        分析一下上面这段代码:

        1.A进行生命周期,实例化A
        2.A依赖了B,但是B是个懒加载的bean,创建一个代理返回给A
        3.A的生命周期结束
        4.B进行生命周期,实例化B
        5.B依赖了A,但是A也是个懒加载对象,创建一个代理返回给B
        6.B的生命周期结束
        7.在A真正使用到B/B真正使用到A的时候,代理对象调用他的getObject方法,返回真正单例池里的真正对象,进行执行

        原型bean的循环依赖也解决了,但是其实是取巧的解决,因为我们不可能在所有的@Autowired上都加上@Lazy的。

        看到这里,你是不是发现,只要把所有的bean都变成懒加载,循环依赖就可以更便捷地解决了?没错,就是这样的,但是前面说了,其实是取巧的解决,因为我们不可能在所有的@Autowired上都加上@Lazy的。

Spring解决了所有的循环依赖问题吗

        看下面一个例子

@Component
public class A {

    @Autowired
    private B b;

    @Async
    public void testA() {
        System.out.println("testA");
    }
}

@Component
public class B {

    @Autowired
    private A a;

    @Async
    public void testB() {
        System.out.println("testB");
    }
}
@ComponentScan("com.bill.spring")
@EnableAsync
public class AppConfig {
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(AppConfig.class);
        A a = (A) applicationContext.getBean("a");
        B b = (B) applicationContext.getBean("b");

        a.testA();
        b.testB();
    }
}

        这段代码最终执行结果如下

八月 02, 2022 8:36:34 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5197848c: startup date [Tue Aug 02 20:36:34 CST 2022]; root of context hierarchy
八月 02, 2022 8:36:35 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: 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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
Exception in thread "main" 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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:585)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
	at com.bill.spring.Main.main(Main.java:9)

Process finished with exit code 1

        把@Async去掉,执行结果就正常了

八月 02, 2022 8:37:32 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5197848c: startup date [Tue Aug 02 20:37:32 CST 2022]; root of context hierarchy
testA
testB

Process finished with exit code 0

        很神奇的一个现象,那这是为什么呢?

        原因和AOP提前的理由很像——@Async这个注解的原理也是要生成一个代理。本身A已经被注入到其他Bean的成员变量里了,这里又要改变A,Spring不允许你这么干,所以就报错了。

        所以,我们要明白一个事情——Spring里不是只有AOP才会生成代理对象,还有很多步骤会生成代理对象的。

        那问题又来了:为什么spring要解决AOP带来的循环依赖问题,而不解决@Async这个注解带来的问题呢?也许是因为Spring觉得@Async这个注解我们用得少,AOP功能我们用得多吧。其实我们反过来想,如果Spring要把这些后置的代理逻辑都要处理掉,那得是一件多麻烦的事——不仅要解决当前版本的逻辑,后续加其他代理逻辑的时候也得小心翼翼的,干脆一了百了——出现了循环依赖的情况下,都不允许你后面出现代理对象了。

        所以,这里我们可以看到,Spring只是解决了AOP情况下的循环依赖,其他步骤如果产生了代理对象,Spring是没有解决的

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring循环依赖指的是在Spring中,多个Bean之间存在相互依赖的情况。具体来说,当一个Bean A依赖于另一个Bean B,同时Bean B也依赖于Bean A时,就形成了循环依赖。这种情况下,Spring需要解决Bean的创建和依赖注入的顺序问题。 在Spring中,循环依赖问题是由于Bean的生命周期所引起的。SpringBean生命周期包括了Bean的实例化、属性注入、初始化以及销毁等过程。当出现循环依赖时,Spring会通过使用“提前暴露”的方式来解决这个问题。 具体来说,当Spring创建Bean A时,发现它依赖于Bean B,于是会创建一个A的半成品对象,并将其暂时放入一个缓存中。然后,Spring会继续创建Bean B,并将其注入到A的属性中。接着,Spring会继续完成B的创建,并将其放入缓存中。最后,Spring会将A的半成品对象交给B进行依赖注入,完成A的创建,并将其从缓存中移除。 需要注意的是,Spring循环依赖有一定的限制条件。例如,如果Bean A和Bean B都是单例模式,那么它们之间的循环依赖是无法解决的。因为单例模式下,Bean的创建和依赖注入是同时进行的,无法通过缓存来解决循环依赖。在这种情况下,程序员需要手动调整Bean的依赖关系或使用其他解决方案来避免循环依赖的问题。 综上所述,Spring循环依赖是指在Spring中多个Bean之间存在相互依赖的情况。Spring通过使用缓存和提前暴露的方式来解决循环依赖问题,但在某些情况下有一定的限制条件需要注意。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值