Spring在注入bean的时候会做循环依赖检查,例如A依赖B,B依赖C,C依赖A,这就形成了一个循环依赖,Spring会抛出异常。
那么Spring是怎么做到循环依赖检查的呢?我们先来考虑,Spring是如何实现注入功能的,在注入A之前,如果A依赖B那么需要先注入B,然后依次类推下去,明显,这是一个递归的调用,Spring在其BeanFactory的实现类中,对getBean方法递归调用实现注入,代码如下:
// spring 依赖注入 DFS
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
if (isDependent(beanName, dependsOnBean))
{
// 有循环依赖,抛异常
}
//注册dependsOnBean 的前驱为beanName
regirsert(dependsOnBean,beanName)
getBean(dependsOnBean);// 递归调用
}
}
如上所示,这其实是一个有向图的深度遍历算法,红色部分实现了循环依赖检查,有兴趣的同学可以自己去看下具体的实现,我这里仅给出形式化解释和伪代码。
到这里,这个问题就转化为有向图是否存在环的问题。isDependent方法参数传入两个节点,如果有环返回true,否则返回false。我来尝试形式化的描述这个问题的解
有向图环检查形式化描述 写道
->符号记为一个有向的连接
我们尝试连接A->B,那么以下两种情况可判断为有环:
1.如果有B->A,
2.存在某个X->A使B->X。
我们尝试连接A->B,那么以下两种情况可判断为有环:
1.如果有B->A,
2.存在某个X->A使B->X。
算法采用一个map来存某个节点的所有前驱节点,以下是伪代码描述
写道
// 尝试A->B,如果存在B->A返回true
isDependent(String A, String B, Set<String> alreadySeen) {
if alreadySeen.Has(A) then // 略过所有已访问过的前驱
return false;
if map = null then
return false;
if map.get(A).has(B) then // 如果有B->A则有环
return true;
for X in map.get(A)// 寻找A的所有前驱,X->A
if isDependent(X,B) then // 如果有B->X则有环
return true;
alreadySeen.add(A)// 标记为已访问
return false;
}
isDependent(String A, String B, Set<String> alreadySeen) {
if alreadySeen.Has(A) then // 略过所有已访问过的前驱
return false;
if map = null then
return false;
if map.get(A).has(B) then // 如果有B->A则有环
return true;
for X in map.get(A)// 寻找A的所有前驱,X->A
if isDependent(X,B) then // 如果有B->X则有环
return true;
alreadySeen.add(A)// 标记为已访问
return false;
}