详解Spring的循环依赖

导读:本篇文章讲解 详解Spring的循环依赖,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

在介绍循环依赖之前,需要简单介绍一下AOP的原理,如果Spring项目使用@EnableAspectJAutoProxy开启了AOP,那么Spring启动的过程中,会创建一个AnnotationAwareAspectJAutoProxyCreator实例,该实例同样继承于BeanPostProcessor,所以在初始化后,会调用该实例的postProcessAfterInitialization()方法,而AOP生成代理对象的实现就是在该方法中完成

1、场景再现

如下所示,Spring创建OrderService实例,属性填充时会去创建UserService的实例,而UserService的创建过程中,属性填充时又依赖于OrderService的实例,导致出现了循环依赖

@Component
public class OrderService {
    @Autowired
    UserService userService;
}

@Component
public class UserService {
    @Autowired
    OrderService orderService;
}

2、 循环依赖解决方案

《详解CreateBean方法》中介绍了Bean创建的完整过程,大概包括以下步骤(以上面的OrderService和UserService为例):

# 创建OrderServce,执行OrderService的生命周期
1、实例化OrderService  得到一个原始对象
2、填充UserService属性 -->先去单例池找UserService -->没有则创建UserService
3、填充其他属性(如果需要)
4、初始化前、初始化
5、初始化后
6、放入单例池

# 创建UserService,执行UserService的生命周期
1、实例化UserService  得到一个原始对象
2、填充OrderService属性 -->先去单例池找OrderService -->没有则创建OrderService
3、填充其他属性
4、初始化前、初始化
5、初始化后
6、放入单例池

上面的流程,OrderService创建的过程中会实例化得到一个原始对象,我们可以将该原始对象缓存起来,然后UserService创建的时候,如果从单例池中取不到OrderService实例,可以从原始对象的缓存池中拿到OrderService对象,然后UserService就可以按照生命周期的步骤继续往下走,OrderService也可以获得实例化后的UserService进行填充,似乎循环依赖就已经解决了呀???

这种方式,在没有AOP的场景中,确实可以解决循环依赖的问题,但最主要的问题是,如果OrderService的某个方法进行了AOP,文章的开始有介绍过,AOP是在初始化后的操作中完成,会生成一个代理对象,然后放入到单例池中,那么问题来了,UserService中注入的OrderService是一个原始对象,但单例池中存放的却是一个代理对象,对于单例模式的Bean实例来说,这是冲突的,那么如何解决这种这种情况呢?

如果AOP总是放在初始化后这一步来实现,上面的问题将无法解决,但是我们可以把AOP提前,如果出现了循环依赖,就判断当前创建的OrderService实例是否需要进行AOP,如果需要进行AOP,就把AOP的操作提前,生成一个代理对象并缓存;然后UserSevice在属性填充的时候,先从单例池获取,如果没有再从缓存中获取

但是我们在OrderService创建的过程中没法判断是否出现了循环依赖,而在UserService创建的过程中比较好判断是否出现了循环依赖,OrderService开始创建时,标识该Bean正在创建,然后填充UserServcei实例,在UserService创建的过程中,再去获取OrderService的实例时,发现当前实例正处于创建中,由此可以判断出现了循环依赖,当出现了循环依赖的时候,在UserService中提前对OrderService进行AOP生成一个代理对象并缓存,如果OrderService中没有AOP,那么缓存的是原始对象

这样Bean实例填充的过程就是:先从单例池中取,再从缓存池中取,如果缓存中也没有,再去判断是否需要进行AOP,如果需要进行AOP,则生成代理对象缓存,如果不需要,则将原始对象缓存

假设OrderService中还需要注入一个User实例,OrderService与User也形成了循环依赖,在User实例创建的过程中,因为前面UserServcie实例创建的时候已经对OrderService进行了缓存,那么就可以直接从缓存中得到OrderService代理对象或原始,无需再进行AOP判断了

@Component
public class OrderService {
   @Autowired
   UserService userService;

   @Autowired
   User user;
}

@Component
public class User {
	@Autowired
	OrderService orderService;
}

至此我们理解的循环依赖就可以解决了,那么Spring具体是如何处理循环依赖的呢?

3、Spring解决循环依赖

Spring通过三级缓存来解决,这三个缓存池分别是:

1、singletonObjects   		-->单例池
2、earlySingletonObjects	-->二级缓存,存放代理对象或者原始对象
3、singletonFactories		-->三级缓存,存放的是ObjectFactory,这是一个函数式接口,相当于存的是一个lambda表达式

其中二级缓存扮演的角色就是我们上面分析循环依赖解决方案时的缓存池,三级缓存是一个lambda表达式,主要用于判断当前正在创建的实例是否需要进行AOP,如果需要则返回一个代理对象,如果不需要返回一个原始对象,然后缓存在earlySingletonObjects中

其实在解决循环依赖的过程中,还有一个缓存earlyProxyReferences,它主要用于记录某个原始对象是否经历过AOP,前面提到了要解决AOP场景的循环依赖,就必须要把AOP的过程提前进行,但是在初始化后的操作中,还是会进行AOP,所以这里就需要一个缓存记录一下当前原始对象是否经历过了AOP,在初始化后的操作中,判断原始对象是否经历过了AOP,如果已经进行了AOP,就不再重复执行AOP的逻辑

3.1 添加三级缓存

下面看Spring添加三级缓存的源码

在AbstractAutowireCapableBeanFactory的doCreateBean方法中,会判断当前创建的Bean是否出现了循环依赖

其中allowCircularReferences属性是通过@Bean创建实例的时候可以指定的,默认为true,即当前创建的Bean允许循环依赖

判断是否出现了循环依赖也很简单,通过isSingletonCurrentlyInCreation()方法判断当前要创建的Bean实例是否已经处于正在创建的状态,如果返回true,就说明出现了循环依赖

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

当出现循环依赖的时候,并不是直接就判断AOP生成代理对象,然后将代理对象或原始对象放入二级缓存,而是将生成缓存实例的逻辑放入到三级缓存中,等需要的时候再来执行的得到缓存实例

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}

3.2 获取注入实例

属性填充的时候,需要调用getBean()来获取注入实例,后面会调用getSingleton()来获取实例,首先从单例池中来获取,如果单例池中没有并且出现了循环依赖,就从二级缓存earlySingletonObjects去取,如果二级缓存也没有,才会去三级缓存获取ObjectFactory函数接口的lambda表达式,然后执行,即执行getEarlyBeanReference()方法来获取实例对象

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

3.3 执行三级缓存

获取所有SmartInstantiationAwareBeanPostProcessor的实现类,调用getEarlyBeanReference()方法,其中处理AOP的AbstractAutoProxyCreator也实现了SmartInstantiationAwareBeanPostProcessor,调用AbstractAutoProxyCreator的getEarlyBeanReference()判断是否是否需要进行AOP以及生成代理对象

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

getCacheKey主要用于判断当前bean实例是普通bean还是FactoryBean,如果是后者,则需要在beanName前面加上”&”前缀,然后earlyProxyReferences缓存当前bean实例,表示当前bean已经进行过了AOP,如果需要AOP,在初始化后的时候,不会再次执行AOP的逻辑

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

wrapIfNecessary()方法才是真正获取最终注入实例的方法,advisedBeans缓存的是bean是否需要经过了AOP,isInfrastructureClass()用于判断是否需要进行AOP,如果需要进行AOP,则生成一个AOP的代理对象返回,否则返回bean原始对象

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }

   // 当前正在创建的Bean不用进行AOP,比如切面Bean
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // Create proxy if we have advice.
   // 判断当前bean是否存在匹配的advice,如果存在则要生成一个代理对象
   // 此处根据类以及类中的方法去匹配到Interceptor(也就是Advice),然后生成代理对象,代理对象在执行的时候,还会根据当前执行的方法去匹配
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   if (specificInterceptors != DO_NOT_PROXY) {
      // advisedBeans记录了某个Bean已经进行过AOP了
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}

3.4 避免重复AOP

前面提到了出现循环依赖的时候,会提前进行AOP,并将提前进行AOP的原始实例存放在earlyProxyReferences缓存中,而在初始化后的操作中,也会调用AbstractAutoProxyCreator的postProcessAfterInitialization()

如果当前的bean实例与earlyProxyReferences中缓存的实例相同,说明是没有经过AOP的,那么将调用wrapIfNecessary()判断是否需要进行AOP,如果不相等,说明已经提前进行了AOP,不能再重复执行AOP,直接将当前的代理对象返回

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

3.5 实例一致性校验

如果出现了循环依赖,要保证二级缓存里面的实例与最终返回的实例是一致的,如果出现了循环依赖,二级缓存里面存放的原始对象或经过AOP生成的代理对象

但如果在出现循环依赖的类中使用了@Async,该注解的处理类是AsyncAnnotationBeanPostProcessor,同样是一个BeanPostProcessor的实现类,那么在初始化后的操作中就会去调用postProcessAfterInitialization(),生成一个代理对象返回

那么此时二级缓存中的实例与返回的实例不是同一个实例,就会抛出异常

// 初始化
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)) {
            // beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
            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 " +
                                                           "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
        }
    }
}

对于上面的这种情况,可以通过在出现循环依赖的注入点上添加@Lazy,这样属性填充的时候,注入的是一个代理对象,就不会出现循环依赖了

4、番外

Spring中哪些循环依赖是无法解决的呢?

1、从上面的例子可以看出,解决循环依赖,首先是能通过实例化获取一个原始对象,如果原始对象都无法创建,那么循环依赖自然没法解决,所以如果通过构造方法来注入Bean实例时,出现了循环依赖是无法解决的,因为基本的原始对象都无法创建出来

2、判断是否出现了循环依赖的前提是,当前创建的Bean实例是单例,如果是原型Bean,或者其他类型的Bean,就不会出现Spring中的循环依赖

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/112160.html

(0)
Java光头强的头像Java光头强

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!