Spring中的循环依赖

首先看一下出问题的代码片段:

@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

这两段代码中定义了两个Service类:TestService1和TestService2,在TestService1中注入了TestService2的实例,同时在TestService2中注入了TestService1的实例,这里构成了循环依赖。
只不过,这不是普通的循环依赖,因为TestService1的test1方法上加了一个@Async注解。程序运行结果:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

但如果把@Async注解去掉,又能正常启动。其实就算去掉@Async注解也已经构成循环依赖,但为什么还能正常启动呢?因为Spring的内部机制已经帮我们解决了这个问题,spring内部有三级缓存:

  • singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
  • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
  • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象

但Spring只能解决单例类型的循环,其解决办法就是提前暴露ObjectFactory,将未填充完属性的bean提前暴露出来。

for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        FactoryBean<?> factory = (FactoryBean<?>) bean;
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged(
                                    (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

由此可以看到,只有非抽象、单例 并且非懒加载的类才能被提前初始bean。(所以多例模式也是可以正常启动的,不会被提前初始化bean;如果想让它提前初始化bean,只要再定义一个单例,注入多例依赖即可,其启动就会报循环依赖的错误)


image.png
image.png

这里为什么要用第二级缓存呢?
试想一下,如果出现以下这种情况,我们要如何处理?
TestService1依赖于TestService2和TestService3,而TestService2依赖于TestService1,同时TestService3也依赖于TestService1。
按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。
假设不用第二级缓存,TestService1注入到TestService3的流程如图:


image.png

TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
这样不是有问题?
为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。


image.png

还有个问题,第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?
答:不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。
针对这种场景spring是怎么做的呢?
答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:
if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。

回到问题中来,@Async注解的场景,会通过AOP自动生成代理对象:


image.png

说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了,但是在这里是关键点。

如果这时候把TestService1改个名字,改成:TestService6,其他的都不变:

@Service
publicclass TestService6 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}

再重新启动一下程序,神奇般的好了。
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载。
为什么TestService2比TestService6先加载就没问题呢?
答案在下面这张图中:


image.png

这种情况testService6中其实第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。


解决这类问题一般使用:

  • 使用@Lazy注解,延迟加载
  • 使用@DependsOn注解,指定加载先后关系
  • 修改文件名称,改变循环依赖类的加载顺序

转载整理自:https://mp.weixin.qq.com/s/fX4ze5YuLI5cqVaZhxq1tw

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容