资讯专栏INFORMATION COLUMN

一起来读Spring源码吧(四)循环依赖踩坑笔记

jas0n / 1676人阅读

摘要:从创建的的起点开始首先会在缓存中查找,这里一共有三级缓存,保存初始化完成的单例实例,保存提前曝光的单例实例,保存单例的工厂函数对象后面两级都是为了解决循环依赖设置的,具体查找逻辑在后续其他情况下调用会说明。

源起

在开发过程中,遇到需要把方法调用改为异步的情况,本来以为简单得加个@Asyn在方法上就行了,没想到项目启动的时候报了如下的错误:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name "customerServiceImpl": 
Bean with name "customerServiceImpl" has been injected into other beans [customerServiceImpl,followServiceImpl,cupidService] 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.

看了下好像报的是循环依赖的错误,但是Spring单例是支持循环依赖的,当时一脸懵逼。
拿着报错去百度了下,说是多个动态代理导致的循环依赖报错,也找到了报错的地点,但是还是不明白为什么会这样,所以打算深入源码探个究竟,顺便回顾下Bean的获取流程和循环依赖的内容。

模拟场景

用SpringBoot新建一个demo项目,因为原项目是有定义切面的,这里也定义一个切面:

@Aspect
@Component
public class TestAspect {

    @Pointcut("execution(public * com.example.demo.service.CyclicDependencyService.sameClassMethod(..))")
    private void testPointcut() {}

    @AfterReturning("testPointcut()")
    public void after(JoinPoint point) {
        System.out.println("在" + point.getSignature() + "之后干点事情");
    }

}

然后新建一个注入自己的Service构成循环依赖,然后提供一个方法满足切点要求,并且加上@Async注解:

@Service
public class CyclicDependencyService {

    @Autowired
    private CyclicDependencyService cyclicDependencyService;

    public void test() {
        System.out.println("调用同类方法");
        cyclicDependencyService.sameClassMethod();
    }

    @Async
    public void sameClassMethod() {
        System.out.println("循环依赖中的异步方法");
        System.out.println("方法线程:" + Thread.currentThread().getName());
    }

}

还有别忘了给Application启动类加上@EnableAsync和@EnableAspectJAutoProxy:

@EnableAsync
@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

最后打好断点,开始debug。

debug

从Bean创建的的起点--AbstractBeanFactory#getBean开始

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

首先会在缓存中查找,DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference):

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

这里一共有三级缓存:

singletonObjects,保存初始化完成的单例bean实例;

earlySingletonObjects,保存提前曝光的单例bean实例;

singletonFactories,保存单例bean的工厂函数对象;

后面两级都是为了解决循环依赖设置的,具体查找逻辑在后续其他情况下调用会说明。

缓存中找不到,就要创建单例:

sharedInstance = getSingleton(beanName, () -> {
   try {
      return createBean(beanName, mbd, args);
   }
   catch (BeansException ex) {
      // Explicitly remove instance from singleton cache: It might have been put there
      // eagerly by the creation process, to allow for circular reference resolution.
      // Also remove any beans that received a temporary reference to the bean.
      destroySingleton(beanName);
      throw ex;
   }
});

调用DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory singletonFactory):

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
    ...
    beforeSingletonCreation(beanName);
    ...
    singletonObject = singletonFactory.getObject();
    ...
    afterSingletonCreation(beanName);
    ...
    addSingleton(beanName, singletonObject);
    ...
}

创建前后分别做了这几件事:

前,beanName放入singletonsCurrentlyInCreation,表示单例正在创建中

后,从singletonsCurrentlyInCreation中移除beanName

后,将创建好的bean放入singletonObjects,移除在singletonFactories和earlySingletonObjects的对象

创建单例调用getSingleton时传入的工厂函数对象的getObject方法,实际上就是createBean方法,主要逻辑在AbstractAutowireCapableBeanFactory#doCreateBean中:

...
instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = instanceWrapper.getWrappedInstance();
...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean "" + beanName +
            "" to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
   populateBean(beanName, mbd, instanceWrapper);
   exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         String[] dependentBeans = getDependentBeans(beanName);
         Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name "" + beanName + "" has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] 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.");
         }
      }
   }
}

可以看到报错就是在这个方法里抛出的,那么这个方法就是重点中的重点。

首先实例化单例,instantiate,只是实例化获取对象引用,还没有注入依赖。我debug时记录的bean对象是CyclicDependencyService@4509

然后判断bean是否需要提前暴露,需要满足三个条件:1、是单例;2、支持循环依赖;3、bean正在创建中,也就是到前面提到的singletonsCurrentlyInCreation中能查找到,全满足的话就会调用DefaultSingletonBeanRegistry#addSingletonFactory把beanName和单例工厂函数对象(匿名实现调用AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法)放入singletonFactories;

接着就是注入依赖,填充属性,具体怎么注入这里就不展开了,最后会为属性cyclicDependencyService调用DefaultSingletonBeanRegistry.getSingleton(beanName, true),注意这里和最开始的那次调用不一样,isSingletonCurrentlyInCreation为true,就会在singletonFactories中找到bean的单例工厂函数对象,也就是在上一步提前暴露时放入的,然后调用它的匿名实现AbstractAutowireCapableBeanFactory#getEarlyBeanReference:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}

方法逻辑就是挨个调用实现了SmartInstantiationAwareBeanPostProcessor接口的后置处理器(以下简称BBP)的getEarlyBeanReference方法。一个一个debug下来,其他都是原样返回bean,只有AnnotationAwareAspectJAutoProxyCreator会把原bean(CyclicDependencyService@4509)存在earlyProxyReferences,然后将bean的代理返回(debug时记录的返回对象是CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740)并放入earlySingletonObjects,再赋给属性cyclicDependencyService。

public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}

属性填充完成后就是调用初始化方法AbstractAutowireCapableBeanFactory#initializeBean:

...
invokeAwareMethods(beanName, bean);
...
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
...
invokeInitMethods(beanName, wrappedBean, mbd);
...
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
...

初始化主要分为这几步:

如果bean实现了BeanNameAware、BeanClassLoaderAware或BeanFactoryAware,把相应的资源放入bean;

顺序执行BBP的postProcessBeforeInitialization方法;

如果实现了InitializingBean就执行afterPropertiesSet方法,然后执行自己的init-method;

顺序执行BBP的postProcessAfterInitialization。

debug的时候发现是第4步改变了bean,先执行AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization:

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;
}

这里会获取并移除之前存在earlyProxyReferences的bean(CyclicDependencyService@4509),因为和当前bean是同一个对象,所以什么都没做直接返回。随后会执行AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization:

if (isEligible(bean, beanName)) {
   ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
   if (!proxyFactory.isProxyTargetClass()) {
      evaluateProxyInterfaces(bean.getClass(), proxyFactory);
   }
   proxyFactory.addAdvisor(this.advisor);
   customizeProxyFactory(proxyFactory);
   return proxyFactory.getProxy(getProxyClassLoader());
}

先判断bean是否有需要代理,因为CyclicDependencyService有方法带有@Async注解就需要代理,返回代理对象是CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273

返回的代理对象赋值给AbstractAutowireCapableBeanFactory#doCreateBean方法内的exposedObject,接下来就到了检查循环依赖的地方了:

if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         String[] dependentBeans = getDependentBeans(beanName);
         Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name "" + beanName + "" has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] 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.");
         }
      }
   }
}

首先从earlySingletonObjects里拿到前面属性填充时放入的bean代理(CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740),不为空的话就比较bean和exposedObject,分别是CyclicDependencyService@4509CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273,很明显不是同一个对象,然后会判断allowRawInjectionDespiteWrapping属性和是否有依赖的bean,然后判断这些bean是否是真实依赖的,一旦存在真实依赖的bean,就会抛出BeanCurrentlyInCreationException。

总结

总结下Spring解决循环依赖的思路:在创建bean时,对于满足提前曝光条件的单例,会把该单例的工厂函数对象放入三级缓存中的singletonFactories中;然后在填充属性时,如果存在循环依赖,必然会尝试获取该单例,也就是执行之前放入的工厂函数的匿名实现,这时候拿到的有可能是原bean对象,也有可能是被某些BBP处理过返回的代理对象,会放入三级缓存中的earlySingletonObjects中;接着bean开始初始化,结果返回的有可能是原bean对象,也有可能是代理对象;最后对于满足提前曝光的单例,如果真的有提前曝光的动作,就会去检查初始化后的bean对象是不是原bean对象是同一个对象,只有不是的情况下才可能抛出异常。重点就在于存在循环依赖的情况下,初始化过的bean对象是不是跟原bean是同一个对象

从以上的debug过程可以看出,是AsyncAnnotationBeanPostProcessor这个BBP在初始化过程中改变了bean,使得结果bean和原bean不是一个对象,而AnnotationAwareAspectJAutoProxyCreator则是在填充属性获取提前曝光的对象时把原始bean缓存起来,返回代理的bean。然后在初始化时执行它的postProcessAfterInitialization方法时如果传入的bean是之前缓存的原始bean,就直接返回,不进行代理。如果其他BBP也都没有改变bean的话,初始化过后的bean就是跟原始bean是同一个对象,这时就会把提前曝光的对象(代理过的)作为最终生成的bean。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77558.html

相关文章

  • Spring IOC 容器源码分析 - 循环依赖的解决办法

    摘要:实例化时,发现又依赖于。一些缓存的介绍在进行源码分析前,我们先来看一组缓存的定义。可是看完源码后,我们似乎仍然不知道这些源码是如何解决循环依赖问题的。 1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的。在本篇文章中,我会首先向大家介绍一下什么是循环依赖。然后,进入源码分析阶段。为了更好的说明 Spring 解决循环依赖的办法,我将会从获取 bean 的方法getB...

    aikin 评论0 收藏0
  • 【转】成为Java顶尖程序员 ,看这10本书就够了

    摘要:实战高并发程序设计这本书是目前点评推荐比较多的书,其特色是案例小,好实践代码有场景,实用。想要学习多线程的朋友,这本书是我大力推荐的,我的个人博客里面二十多篇的多线程博文都是基于此书,并且在这本书的基础上进行提炼和总结而写出来的。 学习的最好途径就是看书,这是我自己学习并且小有了一定的积累之后的第一体会。个人认为看书有两点好处:showImg(/img/bVr5S5);  1.能出版出...

    DTeam 评论0 收藏0
  • Java进阶之路

    摘要:探索专为而设计的将探讨进行了何种改进,以及这些改进背后的原因。关于最友好的文章进阶前言之前就写过一篇关于最友好的文章反响很不错,由于那篇文章的定位就是简单友好,因此尽可能的摒弃复杂的概念,只抓住关键的东西来讲,以保证大家都能看懂。 周月切换日历 一个可以进行周月切换的日历,左右滑动的切换月份,上下滑动可以进行周,月不同的视图切换,可以进行事件的标记,以及节假日的显示,功能丰富 Andr...

    sushi 评论0 收藏0
  • Spring IOC 容器源码分析系列文章导

    摘要:本文是容器源码分析系列文章的第一篇文章,将会着重介绍的一些使用方法和特性,为后续的源码分析文章做铺垫。我们可以通过这两个别名获取到这个实例,比如下面的测试代码测试结果如下本小节,我们来了解一下这个特性。 1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了...

    NSFish 评论0 收藏0
  • Spring Security

    摘要:框架具有轻便,开源的优点,所以本译见构建用户管理微服务五使用令牌和来实现身份验证往期译见系列文章在账号分享中持续连载,敬请查看在往期译见系列的文章中,我们已经建立了业务逻辑数据访问层和前端控制器但是忽略了对身份进行验证。 重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<