写在前面
在了解循环依赖之前,要先对以下几点先有个概念
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注解(懒加载)大致工作原理
- 被@Lazy标记的属性,在populateBean注入依赖时,会直接注入一个 proxy 对象,并不是原生对象本身
- 在真正使用这个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是没有解决的。