Loading

Spring 如何解决循环依赖

Spring 如何解决循环依赖

作者:Grey

原文地址:

博客园:Spring 如何解决循环依赖

CSDN:Spring 如何解决循环依赖

如果X这个类依赖了Y,Y这个类依赖了X,就产生了循环依赖。在普通Java(非Spring框架)下,这并不是一个问题。

参考如下示例代码:

public class Demo {
    public static void main(String[] args) {
        X a = new X();
        Y b = new Y();
        a.y = b;
        b.x = a;
        System.out.println(a);
        System.out.println(b);
    }
}

class X {
    Y y;
}

class Y {
    X x;
}

但是Spring创建对象由于有相对复杂的生命周期,所以可能会导致循环依赖的问题,我们将如上代码转换成使用Spring的方式:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

public class CircularDependenciesDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 将当前类配置为Configuration类
        applicationContext.register(CircularDependenciesDemo.class);
        // 是否支持循环依赖
        // applicationContext.setAllowCircularReferences(true);
        // 启动
        applicationContext.refresh();
        System.out.println(applicationContext.getBean("x"));
        System.out.println(applicationContext.getBean("y"));
        // 关闭上下文
        applicationContext.close();
    }

    @Bean
    public static X x() {
        return new X();
    }

    @Bean
    public static Y y() {
        return new Y();
    }
}

class X {
    @Autowired
    Y y;
}

class Y {
    @Autowired
    X x;
}

运行,正常打印出:

git.snippets.fail1.X@ed9d034
git.snippets.fail1.Y@6121c9d6

以下是循环依赖是否支持的开关,如果设置为true,则支持循环依赖。

// 是否支持循环依赖
applicationContext.setAllowCircularReferences(true);

如果设置为false,再次运行main方法,则报错,以下为简略报错信息:

nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean with name'x':
        Requested bean is currently in creation:Is there an unresolvable circular reference?

通过上述实验,可以了解,Spring解决了循环依赖的问题,如何解决的呢?我们需要先看下Spring中Bean的生命周期:

以x的创建为例:

X阶段:

  1. 解析XML或者注解,将信息注册到BeanDefinition中
  2. 对象的实例化,可以理解成 X x = new X();
  3. x的属性填充(这里就涉及到要填充Y的实例)
  4. x的初始化
  5. Bean后置处理器进行处理(比如AOP)
  6. 把Bean添加到单例池中

在进行到第3步的时候,Spring会从单例池中找Y对应的Bean对象,如果找不到,则会执行y对象的创建过程生命周期,y也会经历和x一样的生命周期:

Y阶段:

  1. 解析XML或者注解,将信息注册到BeanDefinition中
  2. 对象的实例化,可以理解成 Y y = new Y();
  3. y的属性填充(这里就涉及到要填充x的实例)
  4. y的初始化
  5. Bean后置处理器进行处理(比如AOP)
  6. 把Bean添加到单例池中

这里执行到第三步的时候,需要找到x,但是x还没有进入单例池,所以,X阶段卡在第三步无法继续执行,Y阶段也卡在第三步无法继续执行,这就导致了循环依赖问题。

如何解决这个问题?

可以使用一个map,假设叫Tmap,在X阶段第二步的时候,将new出来的x放入这个Tmap中,这样一来,Y阶段的第3步涉及x的实例填充,就可以这样执行:

先从单例池中找x,找不到,再从map中找x,此时因为X阶段的第2步已经把new出来的x放入到Tmap中,所以可以从Tmap中找到,填充即可。 这样,Y阶段可以顺利执行完毕,然后X阶段正常结束

这样就解决了上面提到的循环依赖的问题。

但是会带来一个新的问题,我们用的这个Tmap存放的是X的原始对象,但是,有一种非常常见的情况是,X阶段的第5步,如果做了AOP,此时,会生成一个代理对象,假设叫XProxy,

那么我们需要放入Tmap中的其实是这个XProxy,而且填充Y中x属性也是用的XProxy对象,而非X对应的原始对象,所以刚刚我们使用Tmap这个方案就导致了新的问题:

在X阶段的第5步中,如果存在AOP,那么Y阶段的第3步处理的时候,如何正确的把X的代理对象填充到Y中?

如何解决这个问题呢?

可以考虑提前进行AOP,即:

在X阶段的第2步中,在new出X以后,提前进行AOP,生成代理对象,并且把代理对象放入Tmap中,这样,后续填充的时候,从Tmap中拿出的就是代理对象, 问题就解决了。

但是,新的问题又来了,正常的Bean的生命周期到第5步才进行AOP,怎么判断一个Bean需要提前AOP呢? 由上面的推论可知:只有出现了循环依赖且后置处理器中配置了AOP,才需要进行提前AOP,否则,不需要提前进行AOP,简言之:如果没有出现循环依赖,就不需要提前AOP

那么新的问题又来了,

问题1: 如何判断出现了循环依赖呢?

问题2: 如果已经进行了提前AOP,那么执行到第5步的时候,怎么判断已经执行过了AOP?

先看问题2,Spring中处理Aop的类是AbstractAutoProxyCreator,其中定义了一个Map(earlyProxyReferences)来存已经提前进行了Aop的Bean, 关键代码如下:

 @Override
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    return wrapIfNecessary(bean, beanName, cacheKey);
   }
  }
  return bean;
 }

回到第一个问题:如何判断出现了循环依赖呢?

我们可以在X阶段第2步中加入一个map,在X创建的时候,把x加入一个map,这个map表示正在创建的Bean。

在Y阶段的时候,判断需要X,先去单例池中找,找不到,然后去这个存正在创建bean的map中找X,发现X找到,说明出现了循环依赖,所以需要提前AOP

但是,提前AOP的前提,是需要一个原始对象的,这个原始对象存在哪里? 这就是第三级缓存,即一个Map,其中Key是beanName, Value存的是lambda表达式

在提前AOP的时候,从三级缓存中拿到这个lambda表达式,执行,就拥有了原始对象(aop)

注意:需要提前aop的情况下,lambda表达式得到的是代理对象,不需要提前aop的情况下,得到的就是原始对象。

解决了如上问题,又会引入一个新问题,就是:如果新出现一个Bean,假设叫Z,也依赖X,且我们假设X已经配置了AOP,Z类的代码如下:

class Z {
    @Autowired
    X x;
}

执行Z阶段和执行Y阶段应该是类似的,在第2步判断的时候,都会判断需要进行提前AOP,且会通过三级缓存生成一个X的代理对象,此时Z阶段执行和Y阶段执行都会走这步,这样就导致了会生成两个X的代理对象,

如何保证只有一个代理对象呢? 那么二级缓存登场了,三级缓存生成的代理对象放入这个二级缓存中即可,下次从二级缓存中取出对应代理对象即可。

那么上述的代理对象什么时候放入单例池中呢?

Spring中是这样处理的,Bean先从单例池找,没有找到,二级缓存中找,找到了代理对象,就把代理对象放入单例池,删掉二级缓存中的代理对象即可。

最后,总的流程就是:

先从单例池找,找不到,去二级缓存找,再去三级缓存中找,三级缓存找到,会把对应记录删除,并把代理对象(如果有AOP的话)或者原始对象放入二级缓存。

二级缓存除了提高效率以外,最重要的作用是防止生成两个aop对象

正常情况下,二级三级缓存都没有用,二级三级缓存主要是为了解决循环依赖的问题。

参考资料

终于有人把Spring的循环依赖问题解释清楚了

posted @ 2021-09-13 16:30  Grey Zeng  阅读(1061)  评论(0编辑  收藏  举报