java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring @Async循环依赖

Spring使用@Async出现循环依赖原因及解决方案分析

作者:abments

在Spring框架中,启用异步功能需要在应用主类上添加@EnableAsync注解,当项目中存在循环引用时,如一个异步类MessageService和一个常规类TaskService相互引用,并且这两个类位于同一包内,这种情况下可能会触发Spring的循环依赖异常

场景复现

1、首先项目需要打开spring的异步开关,在application主类上加@EnableAsync
2、创建一个包含了@Async方法的异步类MessageService:

@Service
public class MessageService {
    @Resource    
    private TaskService taskService;   
    @Async    
    public void send(){
        taskService.shit();    
    }
}

3、创建另一个正常类TaskService,与异步类形成循环引用的关系(注意MessageService和TaskService在同一个包内,并且order为默认,因此会先扫描MessageService再扫描TaskService):

@Service
public class TaskService {
    @Resource    
    private MessageService messageService;  
    public void shit(){
        System.out.println();    }
}

4、启动springboot项目成功报错

问题出现的原因

在分析原因之前,我们需要提前知道两个重要的点:

举个例子: T类是个包含了@Transactional方法的类,属于需要被代理的对象,并且通过@Resource(或者@Autowired)的方式依赖了A ,A类中也以同样的方式注入了T,并且T类先于A类开始实例化过程,那么简单的实例化流程就是:

出现本文标题中循环引用异常的原因分析

包含了@Async 方法的类与@Transactional的类相似,也会被替换成一个新的代理类,但是与普通aop不同的是,@Async不会在 getEarlyBeanReference 阶段执行创建代理的逻辑(这么做的原因暂时没仔细分析),而是被延迟到了initializeBean步骤当中(即1.提到的90%的代理情况),这样一来就会导致TaskService注入的并不是最终创建完成的MessageService的代理对象,很明显这样的结果是不合理的,而在代码层面,spring的AbstractAutowireCapableBeanFactory当中,在initializeBean和将bean放入一级缓存之间,有这么一段容易被忽视的代码,用于把控最终的循环引用结果正确性:

//是否允许提前暴露,可以理解为是否允许循环引用
if (earlySingletonExposure) {
    //遍历一到三级缓存,拿到的bean
   Object earlySingletonReference = getSingleton(beanName, false);
    //如果缓存中的对象不为空
   if (earlySingletonReference != null) {
      //exposedObject是执行了initializeBean之后的对象,bean是通过构造器创建的原始对象
        //如果两者相等,则将exposedObject设置为缓存中的对象
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }   //如果两者不是同一个对象,并且不允许直接注入原生对象(默认false),且当前beanName有被其他的bean所依赖
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
        //则获取所有依赖了该beanName的对象
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            //如果这个对象已经处于一级缓存当中,则添加到actualDependentBeans,即依赖该对象的bean是一个走完了整个流程,不会再有机会回炉重做的bean
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
        //最后判断actualDependentBeans是否为空,不为空就抛循环引用的异常
         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.");
         }
      }
   }
}
protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
//如果不是已经完全创建好的bean,就返回true,否则返回false
   if (!this.alreadyCreated.contains(beanName)) {
      removeSingleton(beanName);
      return true;
   }
   else {
      return false;
   }
}

这里就要回到场景复现时提到的:

3、注意MessageService和TaskService在同一个包内,并且order为默认,因此会先扫描MessageService再扫描TaskService。

为什么@Lazy可以解决这个问题

@Lazy 被大多数人理解为:当使用到的时候才会加载这个类。

这个也算是spring希望我们看到的,但是这个描述实际上不完全准确。举个例子:

@Service
public class TaskService {
    @Resource    
    @Lazy
    private MessageService messageService;  
    public void shit(){
        System.out.println();
    }
}

到此这篇关于Spring使用@Async出现循环依赖原因以及解决方案的文章就介绍到这了,更多相关Spring @Async循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文