Spring 在哪些状况下会出现循环依赖错误?哪些状况下能自身解决循环依赖,又是如何解决的?本文将介绍笔者经过本地调试 Spring 源码来观察循环依赖的过程。java
首先本地准备好一份 Spring 源码,笔者是从 Github 上 Clone 下来的一份,而后用 IDEA 导入,再建立一个 module 用于存放调试的代码。spring
本次调试有三个类,A、B 经过注解 @Autowired 标注在属性上构成循环依赖,Main 为主函数类。缓存
@Component("A")
public class A {
@Autowired
B b;
}
复制代码
@Component("B")
public class B {
@Autowired
A a;
}
复制代码
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
A a = (A) context.getBean("A");
B b = (B) context.getBean("B");
}
}
复制代码
能够先试着运行下,并不会报错,说明这种状况下的循环依赖能由 Spring 解决。函数
咱们要观察如何解决循环依赖,首先须要知道 @Autowired
标注的属性是如何注入的,如 B 是怎么注入到 A 中的。post
因为 A、B 的 scope 是 single,且默认 non-lazy,因此在 ClassPathXmlApplicationContext
初始化时会预先加载 A、B,并完成实例化、属性赋值、初始化等步骤。ClassPathXmlApplicationContext
的构造方法以下:this
其中 refresh
是容器的启动方法,点进去,而后找到咱们须要的那一步,即实例化 A、B 的步骤:spa
finishBeanFactoryInitialization
会先完成工厂的实例化,而后在最后一步实例化 A、B:3d
preInstantiateSingletons
将依次对 non-lazy singleton 依次实例化,其中就有 A、B:调试
A、B 不是工厂类,则直接经过 getBean
触发初始化。首先会触发 A 的初始化。code
getBean
=> doGetBean
,再经过 getSingleton
获取 Bean。注意在 doGetBean
中有两个 getSingleton
方法会前后执行,本文用 getSingleton-C
和 getSingleton-F
来区分。第一个是尝试从缓存中获取,这时缓存中没有 A,没法得到,则执行第二个,经过工厂得到。
public Object getSingleton(String beanName) 复制代码
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 复制代码
这里会执行 getSingleton-F
来获取单例 Bean:
这里为 getSingleton-F
传入了个 Lambda 表达式给 ObjectFactory
接口类型的 singletonFatory
。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 复制代码
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
复制代码
因此将会建立一个 ObjectFactory
的匿名类对象 singletonFactory
,而其 getObject
方法的实现将调用 createBean
。
getObject
=> createBean
=> doCreateBean
,建立 A 的 Bean,关于 doCreateBean
,在 《如何记忆 Spring 的生命周期》 中有进行介绍。主要有 4 个步骤:(1)实例化,(2)属性赋值,(3)初始化,(4)注册回调函数。下面看下 B 是在哪一步注入到 A 中。
首先看下是否是实例化。
在实例化完成后,bean 中的 b 仍为 null,说明不是实例化。那再看下一步,属性赋值。
在 populateBean
执行后,bean 中的 b 再也不是 null 了,而已是 B 的对象了,并且 b 的 a 属性也不是 null,是此时正在建立的 bean,说明已经成功完成了依赖注入。因此 "@Autowired
标注的属性是如何注入的" 和 "Spring 如何解决循环依赖" 两个问题的答案都在 populateBean
这一步中。那再从新进入 populateBean
看下。
其中会依次调用 BeanPostProcessor
的 postProcessProperties
方法。在 getBeanPostProcessors
返回的 List 中有 AutowiredAnnotationBeanPostProcessor
,将负责 @Autowired
的注入。
AutowiredAnnotationBeanPostProcessor
的 postProcessProperties
方法以下所示:
先找到被 @Autowired
标注的 b 属性,再经过 inject
注入。
进入 inject
方法,因为 A 依赖 B,这里将经过 beanFactory.resolveDependency
得到 B 的 bean。
在成功获取 B 的 Bean 后,再经过反射注入。
如今须要关注的就是 resolveDependency
,这里解决 A => B 的依赖,须要去获取 B,将仍然经过 getBean
获取,和以前说 getBean
获取 A 的过程相似,只是此次换成了 B,调用栈以下:
仍然将经过 doCreateBean
来建立 B 的 bean。
那么问题来了,以前说过 doCreateBean
会进行属性赋值,那么因为 B 又依赖 A,因此须要将 A 注入到 B 中,但是 A 也还正在进行 doGetBean
。那么 Spring 是怎么解决的循环依赖,关注 B 的 populateBean
就能知道答案了。
因为 B 依赖于 A,因此须要将 A 注入 B,该过程和前面说的 ”将 B 注入 A“相似,经过 getBean
来得到 A。
getBean
=> doGetBean
=> getSingleton
,又是熟悉的步骤,但此次 getSingleton-C
中发生了不同的事,可以成功得到 A 的缓存。
首先尝试在 singletoObjects
中获取,失败。接着尝试从 earlySingletonObjects
中获取,失败。最后在 singletonFactories
中获取到 singletonFactory
,并经过 getObject
获取到 A 的 bean。
这三者被称做三级缓存,在 getSingleton-C
方法中会依次从这三级缓存中尝试获取单例 Bean。当从第三级缓存获取到 A 后,会将其从第三级缓存中移除,加入到第二级缓存。
/** Cache of singleton objects: bean name to bean instance. */
// 缓存单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
// 缓存正在建立,还未建立完成的单例Bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
// 缓存单例bean的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码
咱们能够看到 singletonFactories
中存有 A 和 B,它们是何时被加到三级缓存中的呢?就是在 doCreateBean
中作 populateBean
的前一步经过 addSingeletonFactory
把 beanName
和 ObjectFactory
的匿名工厂类加入到第三级缓存中。当调用 singletonFactory.getObject
方法时,将调用 getEarlyBeanReference
获取 A 的 Bean。
getEarlyBeanReference
会返回 A 的引用,虽然 A 还在建立,未完成。
让咱们想下后面会发生的事:
doCreateBean
,将返回,A 成功获取到 B 的 Bean,完成属性赋值,最后完成 A 的 Bean 的建立。最后 A、B 的 Bean 都完成建立。
之因此经过注解属性注入不会存在循环依赖问题,是由于 Spring 记录了正在建立的 Bean,并提早将正在建立的 Bean 的引用交给了须要依赖注入的 Bean,从而完成闭环,让 B 建立成功,不会继续尝试建立 A。
在这个过程当中最关键的是 Bean 的引用,而要有 Bean 的引用便必须完成 doCreateBean
中的第一步实例化。
咱们这里是将 @Autowired
标注在属性上,而依赖注入发生在第二步属性赋值,这时才能成功获取到引用。
下面咱们试下修改 A、B 为构造器注入,让依赖注入发生在第一步实例化中。
@Component("A")
public class A {
B b;
@Autowired
public A(B b) {
this.b = b;
}
}
复制代码
@Component("B")
public class B {
A a;
@Autowired
public B(A a) {
this.a = a;
}
}
复制代码
构造器的注入将发生在 doCreateBean
的第一步 createBeanInstance
,具体方法以下:
获取 A 的构造器,执行 autowireConstructor
。而后调用ConstructorResolver
的 createArgument
方法处理构造函数的参数,因为构造器被 @Autowired
标注,将使用 resolveAutowiredArgument
处理注入参数,接着又是熟悉的步骤,调用栈以下:
处理依赖注入,会经过 getBean
得到 B,在 doCreateBean
中进行 B 实例化。
那咱们就再进入 B 实例化的第一步 createBeanInstance
方法,调用栈以下:
B 的构造方法依赖 A,则尝试经过 doGetBean
获取 A。因为 A 没有在 doCreateBean
中完成实例化,因此 getSingleton-C
中没法得到 A 的缓存,则只能经过 getSingleton-F
方法尝试得到 A。
但在 getSingleton-F
中的 beforeSingletonCreation
方法将对循环依赖进行检查。
singletonsCurrentlyInCreation
是一个 set,因为 A 已经 都在 getSingleton-F
中执行过一遍了,已经被添加到了 singletonsCurrentlyInCreation
,因此这里第二次经过 getSingleton-F
获取 A 时,add 返回 false,将抛出 BeanCurrentlyInCreationException
异常。
对比以上两种方式 “属性注入” 和 “构造器注入”,都是 A => B => A,区别在于 B => A 时,“属性注入” 在 getSingleton-C
中会经过缓存获取到 A 的引用,而 “构造器注入”,则因为不存在 A 引用,也天然没法经过缓存得到,便会尝试再次经过 getSingleton-F
获取,而及时被 beforeSingletonCreation
检查抛出循环依赖异常。