Spring循环依赖引出的问题(转)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring循环依赖引出的问题(转)相关的知识,希望对你有一定的参考价值。

参考技术A 源起

在开发过程中,遇到需要把方法调用改为异步的情况,本来以为简单得加个@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<String> 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<String> 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@4509 和 CyclicDependencyService$$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://segmentfault.com/a/1190000018835760

[转]spring 学习-bean创建-循环依赖-2

转载自 https://www.iflym.com/index.php/code/201208280001.html

上文中对涉及到循环引用的3个方法作了陈述。

在方法1中,对象信息对beanFactory的形式被放入singletonFactories中,这时earlySingletonObjects中肯定没有此对象(因为remove)。

在方法2中,在一定条件下(allowEarlyReference为true)的条件下,对象从singleFactories中的objectFactory中被取出来,同时remove掉,被放入earlySingletonObjects中。这时,earlySingletonObjects就持有对象信息了;当然,如果allowEarlyReference为false的情况下,且earlySingletonObjects本身就没有持有对象的情况下,肯定不会将对象从objectFactory中取出来的。这个很重要,因为后面将根据此信息进行循环引用处理。

在方法3中,对象被加入到singletonObjects中,同时singletonFactories和earlySingletonObjects中都remove掉持有的对象(不管持有与否),这就表示在之前的处理中,这只相当于一个临时容器,处理完毕之后都会remove掉。

那么,我们来看这3个方法是不是按照先后顺序被调用的呢。代码顺序如下所示:

类AbstracBeanFactory获取bean。M-1

protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        }               ……
}

进入getSingleton方法M-2

Object singletonObject = this.singletonObjects.get(beanName);
                try {
//首先执行getObject方法,再执行finnaly中的addSingleton方法,即上文中的方法3
                    singletonObject = singletonFactory.getObject();
                }finally {
addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }

查看singletonFactory.getObject(),即createBean(beanName, mbd, args),最终转向doCreateBean方法M-3

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
                    instanceWrapper = createBeanInstance(beanName, mbd, args);
            addSingletonFactory(beanName, new ObjectFactory() {
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }

上面代码会调用方法addSingletonFactory,即上文所说的方法1。
那么方法2会在什么地方调用呢。答案在两个地点。

第一个地方,称之为调用点A,即在最开始获取bean时,会调用。

Object sharedInstance = getSingleton(beanName);

此方法最终会调用到

getSingleton(beanName, true)

这里传递了参数true。即会尝试解析singletonFactories。然而,在最开始创建对象时,singletonFactories中肯定不会持有对象信息,所以会返回null。

第二个地方,称之为调用点B,即在完成bean创建时,会有一个验证过程。即在方法M-3中,即在调用方法2之前。代码如下:

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<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
                    for (String dependentBean : dependentBeans) {
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }
                    if (!actualDependentBeans.isEmpty()) {
                        throw 文章开头的异常。
                    }
                }
            }

调用点B的逻辑有点多,后面的逻辑主要是作循环引用验证。注意在调用点B传递参数为false,即不会解析singletonFactories。

详细见下一篇

以上是关于Spring循环依赖引出的问题(转)的主要内容,如果未能解决你的问题,请参考以下文章

[转]spring 学习-bean创建-循环依赖-1

[转]spring 学习-bean创建-循环依赖-3

Spring的循环依赖问题

面试题:如何解决Spring 的循环依赖问题

Spring循环依赖

Spring是如何解决循环依赖的?