问题由来
为什么Spring IOC容器在处理循环依赖这块比较复杂,主要是AOP代理(AnnotationAwareAspectJAutoProxyCreator)的引入,它会改变对象的引用地址。
如果按默认的顺序,这个代理类是在装配属性(populateBean)之后才执行的,所以Spring把生成代理这块的逻辑放在一个lambda表达式里,即一个ObjectFactory匿名类里() -> getEarlyBeanReference(beanName, mbd, bean),如果存在循环引用,这个lambda表达式就会提前执行,底层是调用AnnotationAwareAspectJAutoProxyCreator.getEarlyBeanReference来返回代理对象的引用。
基本概念
在DefaultSingletonBeanRegistry里有三个Map
// 一级缓存: 完全实例化的单例对象集合
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 三级缓存:存放获取早期引用的回调
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 二级缓存:存放早期Bean在引用,尚未完成属性装配
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
处理逻辑
假设前提
假定有两个相互依赖的类,都采用懒加载的方式来实例化
@Service
@Lazy
public class A {
@Autowired
private B a;
}
//-------------------------
@Service
@Lazy
public class B {
@Autowired
private A a;
}
调用方式
@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringComponentScanApp {
private static ApplicationContext applicationContext;
public static void main(String[] args) {
applicationContext = new AnnotationConfigApplicationContext(SpringComponentScanApp.class);
applicationContext.getBean(A.class);
}
}
逻辑描述
1、在获取A在实例时,开始会从第一、第二、第三级缓存中按顺序去找。
2、从缓存中查找无果后,开始创建A的实例。
3、创建完A的实例后,会将一个lambda表达式表示的ObjectFactory存入到第三缓存中。
4、装配A实例的属性,在装配过程中发现需要装配B在实例。
5、从第一、第二、第三级缓存中按顺序去找B的实例,结果一无所获。
6、创建B的实例,同时会将一个lambda表达式表示的ObjectFactory存入到第三缓存中
7、装配B实例的属性,在装配过程中发现需要装配A的实例
8、从第一、第二、第三级缓存中按顺序去找A的实例,结果在第三缓存中发现了A的ObjectFactory对象。
9、调用A的ObjectFactory对象的getObject()方法,得到A的早期对象引用。
10、给B的实例装配完A的实例之后,初始化B的实例。
11、将B的实例缓存至第一缓存,同时删除第二、第三缓存。
12、给A的实例装配完B的实例之后,初始化A。
13、将A的实例缓存至第一缓存,同时删除第二、第三缓存。
参考图形