带有@Transactional和@Async的循环依赖问题的解决

论坛 期权论坛 脚本     
niminba   2021-5-26 09:49   1012   0

今天我们来探讨一个有意思的spring源码问题,也是一个学生告诉了我现象我从源码里面找到了这个有意思的问题。
首先我们看service层的代码案例,如下:

@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {

  @Autowired
  TransationService transationService;

  @Transactional
  @Async
  @Override
  public void transation() {
  }
}

在transation方法上面加上了@Transactional和@Async两个注解,然后在TransationServiceImpl 类中自己把自己的实例注入到transationService属性中,存在循环依赖,理论上单例的循环依赖是允许的。但是我们启动容器会报错,测试代码如下:

public class MyTest {
  @Test
  public void test1() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanBean.class);
  }
}

@Component
@ComponentScan(basePackages = {"com.xiangxue"})
public class ComponentScanBean {
}

然后右键运行test1单元测试加载spring容器就会报错,报错信息如下:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘transationServiceImpl': Bean with name ‘transationServiceImpl' has been injected into other beans [transationServiceImpl] 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.
从报错的字面意思来看,是存在了多版本的循环依赖,如果要解决这个问题,我们必须追溯到源码中。

首先我们从TransationServiceImpl 实例化开始讲起。
实例化从getBean方法看起,前面代码我就不贴了,这篇文章是给读过spring源码的人看的,没读过也看不懂,哈哈 。

1、首先第一次创建TransationServiceImpl实例的时候会从缓存中获取实例 ,如果缓存里面有实例则直接返回,第一次创建的时候缓存中是没有实例的,所以会走到else代码块中。


这里是从三个缓存中获取实例化的详细代码。后面会分析

 @Nullable
 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  //根据beanName从缓存中拿实例
  //先从一级缓存拿
  Object singletonObject = this.singletonObjects.get(beanName);
  //如果bean还正在创建,还没创建完成,其实就是堆内存有了,属性还没有DI依赖注入
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   synchronized (this.singletonObjects) {
    //从二级缓存中拿
    singletonObject = this.earlySingletonObjects.get(beanName);

    //如果还拿不到,并且允许bean提前暴露
    if (singletonObject == null && allowEarlyReference) {
     //从三级缓存中拿到对象工厂
     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
     if (singletonFactory != null) {
      //从工厂中拿到对象
      singletonObject = singletonFactory.getObject();
      //升级到二级缓存
      System.out.println("======get instance from 3 level cache->beanName->" + beanName + "->value->" + singletonObject );
      this.earlySingletonObjects.put(beanName, singletonObject);
      //删除三级缓存
      this.singletonFactories.remove(beanName);
     }
    }
   }
  }
  return singletonObject;
 }

2、第一次进来缓存中没有则创建TransationServiceImpl的实例


最终会走到doCreateBean方法中进行实例化,部分代码如下

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
   throws BeanCreationException {

  ............非关键代码不贴了

  // Eagerly cache singletons to be able to resolve circular references
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
  //是否 单例bean提前暴露
  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");
   }
   //这里着重理解,对理解循环依赖帮助非常大,重要程度 5  添加三级缓存
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  // Initialize the bean instance.
  Object exposedObject = bean;
  try {
   //ioc di,依赖注入的核心方法,该方法必须看,重要程度:5
   populateBean(beanName, mbd, instanceWrapper);

   //bean 实例化+ioc依赖注入完以后的调用,非常重要,重要程度:5
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
  catch (Throwable ex) {
   if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
    throw (BeanCreationException) ex;
   }
   else {
    throw new BeanCreationException(
      mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
   }
  }

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

  ............非关键代码不贴了

  return exposedObject;
 }

由于业务类有循环依赖


所以在第一次实例化业务类的时候,在populateBean(beanName, mbd, instanceWrapper);进行依赖注入时会触发TransationServiceImpl业务类的getBean操作,也就是会调用TransationServiceImpl业务类的getBean方法,第二次会走到TransationServiceImpl实例化的逻辑中。这里明白的刷朵鲜花敲个1,哈哈。

但是在触发第二次业务类的getBean操作之前,还有一个非常重要的步骤,就是业务类的提前暴露,也就是三级缓存的建立。这块会建立业务类和ObjectFactory的映射关系这个建立映射关系是在依赖注入之前!!!!


3、循环依赖注入触发TransationServiceImpl类的第二次getBean获取实例化的逻辑
第二次进来的时候,由于第一次实例化的时候在三级缓存中建立了映射关系,所以第二次会从缓存中获取实例


ObjectFactory对象的getObject方法就会调用到。getEarlyBeanReference方法,这个方法是会从BeanPostProcessor中获取实例,这里可能就会返回代理实例


三级缓存的getObject方法会调用到getEarlyBeanReference中,断点一下,看看。


从断点看,
3:是获取事务代理的BeanPostProcessor类型是SmartInstantiationAwareBeanPostProcessor类型的,所以事务代理的BeanPostProcessor会进来,然后生成代理
4:是获取@Async异步代理的BeanPostProcessor,但是不是SmartInstantiationAwareBeanPostProcessor类型的,所以这里if就不会进来,所以最后这里从三级缓存中拿到的是事务切面的代码对象,注意这里是类中的依赖注入的实例是事务切面的代理实例,如图:


可以看到,这里的advisors切面容器明显是一个事务切面,所以业务类中依赖注入的是一个事务切面的代理实例。
但是在这里我还是要说一下,在生成事务代理的时候其实是有做缓存的,如下代码:


这里的cacheKey就是TransationServiceImpl业务类的bean的名称的字符串,然后会把这个字符串加入到一个earlyProxyReferences的Set容器中

在这里已经在TransationServiceImpl的第二次getBean的时候从三级缓存中获取到了代理对象了,那么第二次的实例化已经完成了,并且已经依赖注入到了TransationServiceImpl的属性中了,这时候依赖注入已经完成了,好,我们还是接着第一次TransationServiceImpl的实例来讲,贴代码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
   throws BeanCreationException {

  ............非关键代码不贴了

  // Eagerly cache singletons to be able to resolve circular references
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
  //是否 单例bean提前暴露
  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");
   }
   //这里着重理解,对理解循环依赖帮助非常大,重要程度 5  添加三级缓存
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  // Initialize the bean instance.
  Object exposedObject = bean;
  try {
   //ioc di,依赖注入的核心方法,该方法必须看,重要程度:5
   populateBean(beanName, mbd, instanceWrapper);

   //bean 实例化+ioc依赖注入完以后的调用,非常重要,重要程度:5
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
  catch (Throwable ex) {
   if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
    throw (BeanCreationException) ex;
   }
   else {
    throw new BeanCreationException(
      mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
   }
  }

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

  ............非关键代码不贴了

  return exposedObject;
 }

也就是populateBean(beanName, mbd, instanceWrapper);依赖注入已经完成了,代码接着往下走。
代理会执行到:

//bean 实例化+ioc依赖注入完以后的调用,非常重要,重要程度:5
exposedObject = initializeBean(beanName, exposedObject, mbd);

在这里,业务类会在这个方法里面再次生成代理,这里就有意思了。代码如下

 protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
  if (System.getSecurityManager() != null) {
   AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
    invokeAwareMethods(beanName, bean);
    return null;
   }, getAccessControlContext());
  }
  else {
   //调用Aware方法
   invokeAwareMethods(beanName, bean);
  }

  Object wrappedBean = bean;
  if (mbd == null || !mbd.isSynthetic()) {
   //对类中某些特殊方法的调用,比如@PostConstruct,Aware接口,非常重要 重要程度 :5
   wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  }

  try {
   //InitializingBean接口,afterPropertiesSet,init-method属性调用,非常重要,重要程度:5
   invokeInitMethods(beanName, wrappedBean, mbd);
  }
  catch (Throwable ex) {
   throw new BeanCreationException(
     (mbd != null ? mbd.getResourceDescription() : null),
     beanName, "Invocation of init method failed", ex);
  }
  if (mbd == null || !mbd.isSynthetic()) {
   //这个地方可能生出代理实例,是aop的入口
   wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  }

  return wrappedBean;
 }

wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
在这个方法里面可能会生成业务类的代理,我们看看这个方法:

@Override
 public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
   throws BeansException {

  Object result = existingBean;
  for (BeanPostProcessor processor : getBeanPostProcessors()) {
   Object current = processor.postProcessAfterInitialization(result, beanName);
   if (current == null) {
    return result;
   }
   result = current;
  }
  return result;
 }

我们断点看看情况


**效果跟我们预期的一样,第一次实例化的时候,在属性依赖注入的时候会在三级缓存中获取事务的代理对象,从断点看,里面的属性确实是一个事务的代理对象,自己本身是没生成代理的。

由于方法上面有 @Transactional @Async在,3,4两个AOP入口的BeanPostProcessor中会生成相应的代理对象,这里为什么会生成代理对象,就不赘述了,核心思想是获取所有advisors,然后挨个判断advisors的pointCut是否matches这两个注解,matches的思路是看方法上面是否有@Transactional 或@Async注解,如果有则返回true就匹配了,如果能找到匹配的切面则生成bean的代理,但是这里要注意的是,事务切面在这里就不会生成代理了,为什么呢???**看代码


这里会判断earlyProxyReferences的Set容器中是否有这个cacheKey,这个cacheKey就是类的名称,而这个容器在提前暴露的三级缓存获取实例的时候就已经设置进去了,所以Set容器中是有这个类的
所以3的AOP入口这里会原样返回Bean,如图:


OK,有意思的来了,这时候就轮到4这个BeanPostProcessor的异步切面的AOP入口执行了。如图:


在这里就返回了bean的异步切面代理,实例如图:


我解释一下这个截图内容,
exposedObject是异步代理对象,在targetSource是代理对象的目标对象,目标对象中有一个transationService属性,这个属性是一个事务的代理对象,OK,从这里我们发现,我去,一个同样的类,居然生成了两个不同的代理对象,一个是异步的代理对象,一个是事务的代理对象,代理对象居然不一致了。为什么会这样,前面我已经分享得很清楚了

然后在spring中,这种情况默认是不被允许的,代码如下:

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

Object earlySingletonReference = getSingleton(beanName, false);
这里我们前面分析过,这里会从三级缓存中获取到事务代理对象

if (exposedObject == bean) {
 exposedObject = earlySingletonReference;
}

然后这里有个if判断,bean是第一次实例化的bean,是没被initializeBean代理之前的bean


而exposedObject对象是一个异步切面的代理对象


这里两者是不相等的,而这个变量默认是allowRawInjectionDespiteWrapping=false的


所有这里就会抛异常,就是文章前面的那个异常,所有我们找到了为什么会有这么一个异常的出现了。
其实要解决这个异常也比较简单,只要把allowRawInjectionDespiteWrapping这个属性变成true就行了。
如何变了,代码如下:


这是这个变量就为true了 ,就不会抛异常了


但是就会存在一个现象,单元测试中获取到的bean对象和类中依赖注入的对象不是同一个了
这个bean对象是异步代理对象


类中属性的对象是事务切面的代理对象


有意思吧,哈哈 。

如果在类里面没有@Async异步注解,其实就不会有问题,默认是允许单例循环依赖的,为什么没问题

@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {

  @Autowired
  TransationService transationService;

  @Transactional
  @Override
  public void transation() {
    System.out.println(transationService.hashCode());
    System.out.println("s");
  }
}

因为

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

如果只要存在循环依赖,第一次业务类实例化的时候代理对象就是从这里获取的


这个地方

//bean 实例化+ioc依赖注入完以后的调用,非常重要,重要程度:5
   exposedObject = initializeBean(beanName, exposedObject, mbd);

由于三级缓存中建立了缓存了


所以会直接返回对应的bean,没有生成代理。代理对象是从这个获取的


是从提前暴露的三级缓存中获取的代理对象赋值给了第一次实例化的bean对象,所以这个else if中可能出现异常的地方就不会走了,因为这两个bean exposedObject 和 bean是相等的。

到此这篇关于带有@Transactional和@Async的循环依赖问题的解决的文章就介绍到这了,更多相关@Transactional和@Async的循环依赖内容请搜索社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持社区!

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP