spring循环依赖的处理⭐
先从单例模式讨论
Spring创建Bean: Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器整个生命周期内只有一个。这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。
一、Spring循环依赖在创建 Bean 过程中,使用到了三级缓存
1、什么是循环依赖
情况:
- 自己依赖自己的直接依赖
- 两个对象之间的直接依赖
- 多个对象之间的间接依赖
循环依赖最常见场景:单例的setter注入
2、Spring 循环依赖为何使用三级缓存?
Spring 解决循环依赖的核心思想在于提前曝光:
- 在构造 Bean 对象之后,将对象提前曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性和初始化。
- 提前曝光的对象被放入 Map<String, ObjectFactory<?>> singletonFactories 缓存中,这里并不是直接将 Bean 放入缓存,而是包装成 ObjectFactory 对象再放入
spring内部有三级缓存:
- singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例。也就是存放可用的成品Bean
- earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例。半成品Bean,已创建对象,但是未注入属性和初始化。用以解决循环依赖。
- singletonFactories 三级缓存,用于保存bean创建工厂,用来生成半成品Bean并放入到二级缓存。以便于后面扩展有机会创建代理对象。
了解解决循环依赖的核心思想和spring内部有三级缓存,接下来就可以知道Spring中如何创建Bean。
Spring中如何创建Bean:
源码:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定义的。以 com.gyh.general 包下的 OneBean 为例,debug springboot 启动过程,分析 spring 是如何创建 bean 的。
参考源码中 spring 创建 bean 的过程。其中最关键的几步有:
1.getSingleton(beanName, true)
依次从一二三级缓存中查找 bean 对象,如果缓存中存在对象,则直接返回(early);
2.createBeanInstance(beanName, mbd, args)
选一个合适的构造函数,new 实例对象(instance),此时的 instance 中依赖的属性还都是 null,属于半成品;
3.singletonFactories.put(beanName, oneSingletonFactory)
利用上一步的 instance,构建一个 singletonFactory,并将其放到三级缓存中;
4.populateBean(beanName, mbd, instanceWrapper)
填充 bean:为该 bean 定义的属性创建对象或赋值;
5.initializeBean("one",oneInstance, mbd)
初始化 bean:对 bean 进行初始化或其他加工,如生成代理对象(proxy);
6.getSingleton(beanName, false)
依次在一二级缓存中查找,检查是否有因循环依赖导致提前生成的对象,有的话与初始化后的对象是否一致;
二、Spring 用三级缓存解决循环依赖的
我们通过例子学习,单例的setter注入,这种注入方式应该是spring用的最多的,代码如下:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制使用三级缓存,让我们根本无法感知它有问题,因为spring默默帮我们解决了。
用一张图告诉你,spring是如何解决循环依赖的:
三、为什么 Sping 不选择二级缓存方式,而是要额外加一层缓存?
如果要使用二级缓存解决循环依赖,意味着 Bean 在构造完后就创建代理对象,这样违背了 Spring 设计原则。 Spring 结合 AOP 跟 Bean 的生命周期,是在 Bean 创建完全之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,在这个后置处理的 postProcessAfterInitialization 方法中对初始化后的 Bean 完成 AOP 代理。
如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理,但是没有出现循环依赖的情况下,设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象。
是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象。