spring是怎么解决循环依赖的

133 阅读2分钟

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

什么是循环依赖

我们先举个例子,来看一下什么是循环依赖。

@Component
public class DemoA {
    @Autowired
    DemoB b;
}

@Component
public class DemoB {
    @Autowired
    DemoA a;
}

说明:在对象A创建过程中,需要注入B,因为容器中没有B,则去创建B,B创建过程中又需要注入A,而A在等待B的创建,B在等待A的创建,导致两者都无法创建成功,无法加入到单例池供用户使用。

Spring则通过三级缓存来解决循环依赖的问题,另外如果对象的作用范围是Prototype,则无法通过三级缓存解决循环依赖,会抛出BeanCurrentlyInCreationException异常,构造注入的方式,也无法解决循环依赖,只有set注入可以解决。

三级缓存

我们平时说三级缓存感觉有点高大上,其实本质上就是三个map。来我们看透它:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

    //一级缓存(单例池,经过完成生命周期的对象会放入其中)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //二级缓存(刚实例化还未初始化的原始对象会放入其中)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
 
    //三级缓存(存放创建某个对象的工厂)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

上面代码中注释也写的很清楚了,相信大家都可以看的明白。我们来说一下spring bean对象的生命周期大致的过程:

  • getSingleton():从单例池中获取bean对象,如果没有,则进行创建。
  • doCreateBean():创建bean对象。
  • populateBean():填充依赖,如果被填充的对象不存在于单例池,则进行创建。
  • addSingleton():将初始化完成的对象加入到单例池。

我们在描述一下利用三级缓存是如何解决循环依赖的:

  • A 创建过程中需要 B, 于是 A 将自己放到三级缓存里面,去实例化 B

  • B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存

    找到了A,然后把三级缓存中的 A 放到二级缓存,并删除三级缓存中的 A

  • B 顺利初始化完毕,将自己放到一级缓存中(此时 B 中的 A 还是创建中状态,并没有完全初始化),删除三级缓存中的 B

    然后接着回来创建 A,此时 B 已经完成创建,直接从一级缓存中拿到 B,完成 A 的创建,并将 A 添加到单例池,删除二级缓存中的 A

文字看不懂?我们来个图说明:

未命名文件