springboot---循环依赖

目录

  • 0. 序!
  • 1. 循环依赖到底是什么?
  • 2. 检测是否存在循环依赖?
  • 3. spring怎么处理循环依赖?
  • 4. 三级缓存的源码分析
  • 5. 循环依赖下的代理对象创建过程
  • 6. 非循环依赖下的代理对象创建过程
  • 7. 循环依赖的主要场景?
    • 7.1 单例的setter注入
    • 7.2 多例的setter注入
    • 7.3 构造器注入
    • 7.4 单例的代理对象setter注入
    • 7.5 DependsOn循环依赖
  • 8. 出现循环依赖如何解决?
    • 8.1 生成代理对象产生的循环依赖
    • 8.2 使用@DependsOn产生的循环依赖
    • 8.3 多例循环依赖
    • 8.4 构造器循环依赖
  • 9. @Async注解与循环依赖
  • 10. @Async与@Transactional对循环依赖的影响

0. 序!

一个@Async注解引发了项目报错,伪代码如下:

@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

实际报错:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlCustomscantimesServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlQrcodeServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlFlowingServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
	at com.shangjietech.jmlseventeen.JmlseventeenApplication.main(JmlseventeenApplication.java:29)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:51)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlQrcodeServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlFlowingServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:521)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
	at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:239)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
	... 25 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlFlowingServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:521)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
	at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:239)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
	... 41 common frames omitted
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:521)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
	at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:239)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
	... 57 common frames omitted

核心报错其实就一句:

Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.

百度翻译:

org.springframework.beans.factory.Beancourrentlyincreationexception: 创建名为“commonServiceImpl”的bean时出错:名为“commonServiceImpl”的Bean已作为循环引用的一部分在其原始版本中注入到其他Bean[jmlMoneyflowingServiceImpl]中,但最终被包装。这意味着所述其他bean不使用该bean的最终版本。这通常是过于急切的类型匹配的结果,例如,考虑在关闭“allowEagerInit”标志的情况下使用“getBeanNamesForType”。

大白话:

在创建bean名称commonServiceImpl时,发现commonServiceImpl在循环依赖中已经以原始bean实例引用注入到其他bean(jmlMoneyflowingServiceImpl)中,但是最终bean实例commonServiceImpl被进行包装,导致前后不一致,出现了多个版本实例。

但是,如上伪代码,将@Async注解去掉,项目启动则不会报错,即有@Async注解则出现循环依赖,无@Async注解则不出现循环依赖!

由此,我才认识到什么是循环依赖,不得不说spring实在是封装太深了,果然面试经不代表项目经验…

1. 循环依赖到底是什么?

何谓循环依赖,其实就是形成一个闭环依赖,关键在于闭环,只要成为闭环都是循环,仔细想想,死锁也是因为闭环。闭环!!!闭环!!!

  1. 自依赖
    springboot---循环依赖_第1张图片

  2. 相互依赖
    springboot---循环依赖_第2张图片

  3. 多级依赖
    springboot---循环依赖_第3张图片

说白是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。但是请注意,spring是可以自己解决部分循环依赖的,这也是为什么@Async注解去掉伪代码则可以正常启动的原因。

2. 检测是否存在循环依赖?

Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

3. spring怎么处理循环依赖?

spring已经默默的帮我解决了循环依赖,只要在spring的规范下使用,即使出现了循环依赖,项目也是可以正常启动的,原因就是spring的apo和ioc

spring内部有三级缓存:

  • singletonObjects:一级缓存,用于保存实例化、注入、初始化完成的bean实例。
    其作用是保存成熟Bean
  • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例。
    其作用是保存原生的早期Bean,不成熟的Bean
  • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
    其作用是保存代理的Bean

spring三级缓存有四大规则:

  1. 无论是对象实例化还是依赖注入,都是在缓存中逐级寻找(先找一级,再找二级,再中三级,三级没有则初步创建)。
  2. 实例在多级缓存中是移动而非复制,从无到3,从3到2,从2到1,或者从3直接到1。
  3. 依赖注入时如果是被动加载,则不会加入二级缓存,即被动加载时会一步到位直接从三级缓存移动到一级缓存
  4. spring中的ioc注入的实例均为单例,发现不为单例则报错。

springboot---循环依赖_第4张图片

4. 三级缓存的源码分析

Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。

Spring的单例对象的初始化主要分为三步:
springboot---循环依赖_第5张图片

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

  3. initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field循环依赖。

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
首先我们看源码,三级缓存主要指:

//一级
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

//二级
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

//三级
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** 
 * 提前暴露 
 * sprig特意将刚刚创建好的bean实例传入到getEarlyBeanReference匿名内中,因为在获取早期bean的时候,不会涉及到AOP,则返回原本创建好的实例 
 * 但是这里的bean实例还未填充属性与初始化,所以是一个不完成的bean 
 * 这里就是解决循环依赖的一个点,在bean在实例化时,提前将还未完全实例化好的早期bean通过缓存向外界暴露 
 * 这样其他bean在实例化需要依赖时,就可以在缓存中获取到早期的bean实例。 
 */ 
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 

这三级缓存分别指:

  • singletonFactories : 单例对象工厂的cache
  • earlySingletonObjects :提前暴光的单例对象的Cache
  • singletonObjects:单例对象的cache

我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:

// AbstractBeanFactory.doGetBean 
// 尝试从缓存中获取对象A 
Object sharedInstance = getSingleton(beanName); 
 
// DefaultSingletonBeanRegistry.getSingleton 
public Object getSingleton(String beanName) { 
   // 注意第二个参数为true 
   return getSingleton(beanName, true); 
} 
 
protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
   // 1:从单列缓存中换取单列bean 
   Object singletonObject = this.singletonObjects.get(beanName); 
   // 2:如果bean还未完全实例化,并且是处于正在创建中 
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
      synchronized (this.singletonObjects) { 
         // 3:尝试从二级缓存中获取 
         singletonObject = this.earlySingletonObjects.get(beanName); 
         // 4:依然为null,且允许创建早期引用,allowEarlyReference传进来的值为true 
         if (singletonObject == null && allowEarlyReference) { 
            // 5:从三级缓存中获取(从单例工厂中获取早期单例的工厂对象) 
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
            if (singletonFactory != null) { 
               // 6:如果早期的单例工厂对象存在,就通过工厂创建早期的bean 
               // 这里是重点:会调用前面的getEarlyBeanReference来获取A对象实例 
               singletonObject = singletonFactory.getObject(); 
               // 7:然后将bean的半成品单列对象,放入到二级缓存 
               this.earlySingletonObjects.put(beanName, singletonObject); 
               // 8:从三级缓存中删除 
               this.singletonFactories.remove(beanName); 
            } 
         } 
      } 
   } 
  return (singletonObject != NULL_OBJECT ? singletonObject : null);
} 

/**上面的代码需要解释两个参数
 * isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
 * allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
 */

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则从singletonFactories中移除,并放入earlySingletonObjects中。即从三级缓存移动到二级缓存。

因此从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

这个接口在下面被引用

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

这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

5. 循环依赖下的代理对象创建过程

我们都知道Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象。

这里我们结合循环依赖,再分析一下AOP代理对象的创建过程和最终放进容器内的动作,看如下代码:


@Service
public class MyServiceImpl implements MyService {
    @Autowired
    private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

此Service类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy。刚好它又存在自己引用自己的循环依赖的情况。跟进到Spring创建Bean的源码部分,来看doCreateBean()方法:


protected Object doCreateBean( ... ){
    
        ...

        // 如果允许循环依赖,此处会添加一个ObjectFactory到三级缓存里面,以备创建对象并且提前暴露引用
        // 此处Tips:getEarlyBeanReference是后置处理器SmartInstantiationAwareBeanPostProcessor的一个方法,
        // 主要是保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象
        // AOP自动代理创建器此方法里会创建的代理对象

        // 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) { // 需要提前暴露(支持循环依赖),注册一个ObjectFactory到三级缓存
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // 如果发现自己被循环依赖,会执行上面的getEarlyBeanReference()方法,从而创建一个代理对象从三级缓存转移到二级缓存里
        // 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时可以知道exposedObject仍旧是原始对象    populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    
        // 经过这两大步后,exposedObject还是原始对象
        // 注意:此处是以事务的AOP为例
        // 因为事务的AOP自动代理创建器在getEarlyBeanReference()创建代理后,
     // initializeBean() 就不会再重复创建了,二选一,下面会有详细描述)
    
        ...
    
        // 循环依赖校验(非常重要)
        if (earlySingletonExposure) {
                // 前面讲到因为自己被循环依赖了,所以此时候代理对象还存放在二级缓存中
                // 因此,此处getSingleton(),就会把代理对象拿出来
                // 然后赋值给exposedObject对象并返回,最终被addSingleton()添加进一级缓存中
                // 这样就保证了我们容器里缓存的对象实际上是代理对象,而非原始对象

                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {
    
                        // 这个判断不可少(因为initializeBean()方法中给exposedObject对象重新赋过值,否则就是是两个不同的对象实例)
                        if (exposedObject == bean) {                 
                                exposedObject = earlySingletonReference;
                        }
                }
                ...
        }
    
}

以上代码分析的是代理对象有自己存在循环依赖的情况,Spring用三级缓存很巧妙的进行解决了这个问题。

6. 非循环依赖下的代理对象创建过程

如果自己并不存在循环依赖的情况,Spring的处理过程就稍微不同,继续跟进源码:


protected Object doCreateBean( ... ) {
        ...

        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

        ...
    
        // 此处注意,因为没有循环引用,所以上面getEarlyBeanReference()方法不会执行
        // 也就是说此时二级缓存里并不会存在
        populateBean(beanName, mbd, instanceWrapper);

        // 重点在此
        //AnnotationAwareAspectJAutoProxyCreator自动代理创建器此处的postProcessAfterInitialization()方法里,会给创建一个代理对象返回
        // 所以此部分执行完成后,exposedObject() 容器中缓存的已经是代理对象,不再是原始对象
     // 此时二级缓存里依旧无它,更别提一级缓存了
     exposedObject = initializeBean(beanName, exposedObject, mbd);

        ...
    
        // 循环依赖校验
        if (earlySingletonExposure) {
                // 前面讲到一级、二级缓存里都没有缓存,然后这里传参数是false,表示不从三级缓存中取值
                // 因此,此时earlySingletonReference = null ,并直接返回

                // 然后执行addSingleton()方法,由此可知,容器里最终存在的也还是代理对象

                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {
                        if (exposedObject == bean) { 
                                exposedObject = earlySingletonReference;
                        }
                }
             ...
}

根据以上代码分析可知,只要用到代理,没有被循环引用的,最终存在Spring容器里缓存的仍旧是代理对象。如果我们关闭Spring容器的循环依赖,也就是把allowCircularReferences设值为false,那么会不会出现问题呢?先关闭循环依赖开关。


// 它用于关闭循环引用(关闭后只要有循环引用现象将报错)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);

    }
}

关闭循环依赖后,上面代码中存在A、B循环依赖的情况,运行程序会出现如下异常:


Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)  

此处异常类型也是BeanCurrentlyInCreationException异常,但报错位置在DefaultSingletonBeanRegistry.beforeSingletonCreation

我们来分析一下,在实例化A后给其属性赋值时,Spring会去实例化B。B实例化完成后会继续给B属性赋值,由于我们关闭了循环依赖,所以不存在提前暴露引用。因此B无法直接拿到A的引用地址,只能又去创建A的实例。而此时我们知道A其实已经正在创建中了,不能再创建了。所有就出现了异常。对照演示代码,来分析一下程序运行过程:

@Service
public class MyServiceImpl implements MyService {

    // 因为关闭了循环依赖,所以此处不能再依赖自己
    // 但是MyService需要创建AOP代理对象
    //@Autowired
    //private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

其大致运行步骤如下:


protected Object doCreateBean( ... ) {

        // earlySingletonExposure = false  也就是Bean都不会提前暴露引用,因此不能被循环依赖

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

        populateBean(beanName, mbd, instanceWrapper);

        // 若是开启事务,此处会为原生Bean创建代理对象
        exposedObject = initializeBean(beanName, exposedObject, mbd);

        if (earlySingletonExposure) {
                ... 

                // 因为上面没有提前暴露代理对象,所以上面的代理对象exposedObject直接返回。

        }
}

由上面代码可知,即使关闭循环依赖开关,最终缓存到容器中的对象仍旧是代理对象,显然@Autowired给属性赋值的也一定是代理对象。

最后,以AbstractAutoProxyCreator为例看看自动代理创建器实现循环依赖代理对象的细节。

AbstractAutoProxyCreator是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴们应该比较熟悉,该抽象类实现了创建代理的动作:


// 该类实现了SmartInstantiationAwareBeanPostProcessor接口 ,通过getEarlyBeanReference()方法解决循环引用问题

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    ...

    // 下面两个方法是自动代理创建器创建代理对象的唯二的两个节点:

    // 提前暴露代理对象的引用,在postProcessAfterInitialization之前执行
    // 创建好后放进缓存earlyProxyReferences中,注意此处value是原始Bean

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {

            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            this.earlyProxyReferences.put(cacheKey, bean);
            return wrapIfNecessary(bean, beanName, cacheKey);

    }

    // 因为它会在getEarlyBeanReference之后执行,这个方法最重要的是下面的逻辑判断
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {

        if (bean != null) {

            Object cacheKey = getCacheKey(bean.getClass(), beanName);

            // 下面的remove()方法返回被移除的value,也就是原始Bean
            // 判断如果存在循环引用,也就是执行了上面的getEarlyBeanReference()方法,
            // 此时remove() 返回值肯定是原始对象
            
            // 若没有被循环引用,getEarlyBeanReference()不执行
            // 所以remove() 方法返回null,此时进入if执行逻辑,调用创建代理对象方法
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;

    }
    ...
}

根据以上分析可得知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。由上面分析得出结论,在Spring容器中,不论是否存在循环依赖的情况,甚至关闭Spring容器的循环依赖功能,它对Spring AOP代理的创建流程有影响,但对结果是无影响的。也就是说Spring很好地屏蔽了容器中对象的创建细节,让使用者完全无感知。

7. 循环依赖的主要场景?

springboot---循环依赖_第6张图片

7.1 单例的setter注入

这种注入方式应该是spring用的最多的,代码如下:

@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    public void test1() {
    }
}
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

一张图展示,spring是如何解决单例setter循环依赖的:
springboot---循环依赖_第7张图片
感觉相互依赖这种场景中其实第二级缓存作用不大。那么问题来了,为什么要用第二级缓存呢?是的,其目的就是因为多级相互依赖。

@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;
    @Autowired
    private TestService3 testService3;

    public void test1() {
    }
}
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}
@Service
publicclass TestService3 {

    @Autowired
    private TestService1 testService1;

    public void test3() {
    }
}

按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。

假设不用第二级缓存,TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
springboot---循环依赖_第8张图片

这显然不行,为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。
springboot---循环依赖_第9张图片
还有个问题,第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?

不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。

针对这种场景spring是怎么做的呢?

答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:
springboot---循环依赖_第10张图片
它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。

7.2 多例的setter注入

这种注入方法偶然会有,特别是在多线程的场景下,具体代码如下:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    public void test1() {
    }
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

很多人说这种情况spring容器启动会报错,其实是不对的,我非常负责任的告诉你程序能够正常启动。

为什么呢?

其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了preInstantiateSingletons方法
springboot---循环依赖_第11张图片
AbstractAutowireCapableBeanFactory类doCreateBean方法中标红的地方明显能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。

而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。

如何让他提前初始化bean呢?

只需要再定义一个单例的类,在它里面注入TestService1

@Service
publicclass TestService3 {

    @Autowired
    private TestService1 testService1;
}

重新启动,果然出现了循环依赖:

Requested bean is currently in creation: Is there an unresolvable circular reference?

注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

7.3 构造器注入

这种注入方式是spring4.x以上的版本中官方推荐的方式(但是,实际中真的很少用,因为这种循环依赖spring无法解决),具体如下代码:

@Service
publicclass TestService1 {

    public TestService1(TestService2 testService2) {
    }
}
@Service
publicclass TestService2 {

    public TestService2(TestService1 testService1) {
    }
}

运行结果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

出现了循环依赖,为什么呢?

springboot---循环依赖_第12张图片
从图中的流程看出构造器注入只是添加了三级缓存,并没有使用缓存,所以也无法解决循环依赖问题。

7.4 单例的代理对象setter注入

这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。
代码如下:

@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

如上代码程序启动会报错,出现了循环依赖:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] 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.

为什么会循环依赖呢?答案就在下面这张图中:
springboot---循环依赖_第13张图片
说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了,但是在这里是关键点,我们重点说说:
springboot---循环依赖_第14张图片

上面的情况正好是走到这段代码,发现第二级缓存 和 原始对象不相等,所以抛出了循环依赖的异常。

小tip:如果这时候把TestService1改个名字,改成:TestService6,其他的都不变。

@Service
publicclass TestService6 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}

再重新启动一下程序,神奇般的好了。

what? 这又是为什么?

这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载。

为什么TestService2比TestService6先加载就没问题呢?

答案在下面这张图中:
springboot---循环依赖_第15张图片
这种情况testService6中其实第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。----重点就是被动加载时不会加入二级缓存,因为可以一步到位。

7.5 DependsOn循环依赖

还有一种有些特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个时候就可以使用@DependsOn注解。

@DependsOn(value = "testService2")
@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    public void test1() {
    }
}
@DependsOn(value = "testService1")
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

程序启动之后,执行结果:

Circular depends-on relationship between 'testService2' and 'testService1'

这个例子中本来如果TestService1和TestService2都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。

这又是为什么?

答案在AbstractBeanFactory类的doGetBean方法的这段代码中:它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。

8. 出现循环依赖如何解决?

项目中如果出现循环依赖问题,说明是spring默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:
springboot---循环依赖_第16张图片

8.1 生成代理对象产生的循环依赖

这类循环依赖问题解决方法很多,主要有:

  • 使用@Lazy注解,延迟加载
  • 使用@DependsOn注解,指定加载先后关系
  • 修改文件名称,改变循环依赖类的加载顺序

8.2 使用@DependsOn产生的循环依赖

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

8.3 多例循环依赖

这类循环依赖问题可以通过把bean改成单例的解决。

8.4 构造器循环依赖

这类循环依赖问题可以通过使用@Lazy注解解决。

当然最好的解决循环依赖问题最佳方案是从代码设计上规避,但是复杂的系统中有可能没法避免。

9. @Async注解与循环依赖

@Service 
public class B { 
    @Autowired 
    private A a; 
} 
 
@Service 
public class A { 
    @Autowired 
    private B b; 
    @Async 
    public void test () {} 
} 

以上代码中,加了异步注解便会出现循环依赖,不加则不会,一下便是详细解释

  1. 创建A对象时,在填充属性前,会将A对象提前暴露在三级缓存

    // 三级缓存,存放单例早期的bean工厂对象 
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); 
     
    /** 
     * 提前暴露 
     * sprig特意将刚刚创建好的bean实例传入到getEarlyBeanReference匿名内中,因为在获取早期bean的时候,不会涉及到AOP,则返回原本创建好的实例 
     * 但是这里的bean实例还未填充属性与初始化,所以是一个不完成的bean 
     * 这里就是解决循环依赖的一个点,在bean在实例化时,提前将还未完全实例化好的早期bean通过缓存向外界暴露 
     * 这样其他bean在实例化需要依赖时,就可以在缓存中获取到早期的bean实例。 
     */ 
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));  
    
  2. 填充对象A属性时,发现依赖了B对象,则开始创建B对象

  3. 同理,在创建B对象时,发现需要依赖A对象,此时则会尝试从缓存中获取对象A

    // AbstractBeanFactory.doGetBean 
    // 尝试从缓存中获取对象A 
    Object sharedInstance = getSingleton(beanName); 
     
    // DefaultSingletonBeanRegistry.getSingleton 
    public Object getSingleton(String beanName) { 
       // 注意第二个参数为true 
       return getSingleton(beanName, true); 
    } 
     
    protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
       // 1:从单列缓存中换取单列bean 
       Object singletonObject = this.singletonObjects.get(beanName); 
       // 2:如果bean还未完全实例化,并且是处于正在创建中 
       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
          synchronized (this.singletonObjects) { 
             // 3:尝试从二级缓存中获取 
             singletonObject = this.earlySingletonObjects.get(beanName); 
             // 4:依然为null,且允许创建早期引用,allowEarlyReference传进来的值为true 
             if (singletonObject == null && allowEarlyReference) { 
                // 5:从三级缓存中获取(从单例工厂中获取早期单例的工厂对象) 
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
                if (singletonFactory != null) { 
                   // 6:如果早期的单例工厂对象存在,就通过工厂创建早期的bean 
                   // 这里是重点:会调用前面的getEarlyBeanReference来获取A对象实例 
                   singletonObject = singletonFactory.getObject(); 
                   // 7:然后将bean的半成品单列对象,放入到二级缓存 
                   this.earlySingletonObjects.put(beanName, singletonObject); 
                   // 8:从三级缓存中删除 
                   this.singletonFactories.remove(beanName); 
                } 
             } 
          } 
       } 
       return singletonObject; 
    } 
    
  4. 然后将A注入到对象B中

  5. 对象B完成创建、初始化后,继续A的流程,将对象B注入到对象A属性中。

  6. 对象A进行初始化

    至此对象AB均已完成。 
     
    底层的一个大致原理是这样。但我们还是没说明为什么加了@Async注解会报错的问题,前面说过在B对象填充属性时,会去缓存中获取A对象,会调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));中getEarlyBeanReference匿名方法。 
    跟踪源码发现会进入到AbstractAutowireCapableBeanFactory.getEarlyBeanReference中 。
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { 
       Object exposedObject = bean; 
       if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { 
          for (BeanPostProcessor bp : getBeanPostProcessors()) { 
             // 判断是否有实现了SmartInstantiationAwareBeanPostProcessor的BeanPostProcessor 
             // 默认是没有的 
             if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { 
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; 
                // 默认直接返回我们创建来的bean 
                // 这里同样是Spring留给我们的一个扩展点的,希望我们自定义实现接口SmartInstantiationAwareBeanPostProcessor 
                // 然后重写方法getEarlyBeanReference,自定义早期单例的bean应该需要哪些东西,好让其他bean实例化时,得到符合需求的早期单例bean。 
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); 
             } 
          } 
       } 
       return exposedObject; 
    } 
     
    我们断点发现getBeanPostProcessors()有以下20BeanPostProcessors,其中包括了AsyncAnnotationBeanPostProcessors 
    

    springboot---循环依赖_第17张图片
    此时循环到AsyncAnnotationBeanPostProcessors后置处理器时,从图中我们发现
    if (bp instanceof SmartInstantiationAwareBeanPostProcessor) 条件根本不成立,所以就不会执行AsyncAnnotationBeanPostProcessors后置拦截处理器,那么AsyncAnnotationBeanPostProcessors在哪里会进行执行???

    springboot---循环依赖_第18张图片
    遍历完所有的后置处理器后,我们发现返回的对象实例是原始传进来的对象实例,没有进行任何的包装对象实例。
    springboot---循环依赖_第19张图片
    此时对象B填充属性A时,已经在缓存中获取到了对象A,并且对象A会添加进二级缓存中去(前面提到过)。此时会继续B的初始化等操作。 我们发现上面这些步骤完全没有问题,那问题来了,到底是哪一步出现了问题??? 我们可以猜想到,此时A还没进行初始化,那么问题是不是会在初始化环节,继续跟踪源码。

    // AbstractAutowireCapableBeanFactory.doCreateBean 
    // 初始化bean 
    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 { 
          // spring在实例化时,最终会调用感知接口中的方法,将spring容器内相应的组件注入到bean的实例中。 
          invokeAwareMethods(beanName, bean); 
       } 
       // 上面的步骤是不会改变bean对象的,所以可以直接跳过 
     
       Object wrappedBean = bean; 
       if (mbd == null || !mbd.isSynthetic()) { 
          // 1)bean初始化前,通过bean后置处理(before)调整下bean的实例 
          wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); 
       } 
     
       try { 
          // 执行bean的初始化 
          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()) { 
          // 2)bean初始化后,执行bean的后置处理器(after) 
          wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); 
       } 
     
       return wrappedBean; 
    } 
     
    通过源码我们发现,在 12处会对bean实例进行一个扩展。我们通过debug跟踪源码发现在代码1处并没有对原始bean实例进行包装。那么必然是在after中进行包装了。 
    
    

    springboot---循环依赖_第20张图片
    进入after后置处理器瞧瞧,我们发现又是执行各种后置处理器

    // AbstractAutowireCapableBeanFactory 
    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; 
    } 
     
    我们还是主要看AsyncAnnotationBeanPostProcessors

    springboot---循环依赖_第21张图片

    跟进AsyncAnnotationBeanPostProcessors执行源码 
    // AbstractAdvisingBeanPostProcessor 
    public Object postProcessAfterInitialization(Object bean, String beanName) { 
        // 判断advisor通知器是否不为空、判断bean是不是实现了AopInfrastructureBean 
        if (this.advisor != null && !(bean instanceof AopInfrastructureBean)) { 
            // 判断bean是不是实现了Advised,我们这里bean并不是切面的形式,所以条件不成立 
            if (bean instanceof Advised) { 
                Advised advised = (Advised)bean; 
                if (!advised.isFrozen() && this.isEligible(AopUtils.getTargetClass(bean))) { 
                    if (this.beforeExistingAdvisors) { 
                        advised.addAdvisor(0, this.advisor); 
                    } else { 
                        advised.addAdvisor(this.advisor); 
                    } 
     
                    return bean; 
                } 
            } 
            // 此时会进入到这里,注意这里面源码进行了取反 
            // 这里其实就是判断一下bean是否能匹配到对应类型的advisor 
            if (this.isEligible(bean, beanName)) { 
                ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName); 
                if (!proxyFactory.isProxyTargetClass()) { 
                    this.evaluateProxyInterfaces(bean.getClass(), proxyFactory); 
                } 
     
                proxyFactory.addAdvisor(this.advisor); 
                this.customizeProxyFactory(proxyFactory); 
                // 重点:创建了一个代理对象并返回。 
                return proxyFactory.getProxy(this.getProxyClassLoader()); 
            } else { 
                return bean; 
            } 
        } else { 
            return bean; 
        } 
    } 
     
     
    // AbstractBeanFactoryAwareAdvisingPostProcessor 
    protected boolean isEligible(Object bean, String beanName) { 
        // 重点关注一下super.isEligible(bean, beanName) 
        return !AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) && super.isEligible(bean, beanName); 
    } 
     
    //  
    protected boolean isEligible(Object bean, String beanName) { 
        return this.isEligible(bean.getClass()); 
    } 
     
    protected boolean isEligible(Class<?> targetClass) { 
        Boolean eligible = (Boolean)this.eligibleBeans.get(targetClass); 
        if (eligible != null) { 
            return eligible; 
        } else if (this.advisor == null) { 
            return false; 
        } else { 
            // 这里会将返回true 
            eligible = AopUtils.canApply(this.advisor, targetClass); 
            this.eligibleBeans.put(targetClass, eligible); 
            return eligible; 
        } 
    } 
     
    我们通过源码发现AsyncAnnotationBeanPostProcessors返回的是一个cglib的代理对象bean 
    

    springboot---循环依赖_第22张图片
    因此初始化后,返回的是一个cglib增强的bean对象
    springboot---循环依赖_第23张图片
    此时我们已经发现了注入到B对象的A属性是原始的实例,但是A初始化后已经是一个包装过后的实例了(cglib),因为spring默认是单例,这肯定会出问题。初始化完后,发现spring还会进行一次对比,源码如下:

    // AbstractAutowireCapableBeanFactory.doCreateBean 
    if (earlySingletonExposure) { 
       // 依然从缓存中获取,注意这里第二个参数是false,也就是说只能从一级缓存、二级缓存中获取 
       // 因为此时还未放入一级缓存,所以肯定是没有的,只能从二级缓存中获取 
       Object earlySingletonReference = getSingleton(beanName, false); 
       if (earlySingletonReference != null) { 
          // 这里会进行一个比较,看二级缓存中的bean实例是否与初始化后的bean实例相等,此时发现并不相等 
          if (exposedObject == bean) { 
             exposedObject = earlySingletonReference; 
          } 
          // 接下来就会判断这个bean是否有其他bean进行依赖,如果有则说明注入到其他bean的依赖不是最终包装过后的bean 
          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 " + 
                      "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); 
             } 
          } 
       } 
    } 
     
    至此,问题的原因我们已经找到。那么如何解决了??? 
    如何解决 
    通过懒加载的形式。代码如下: 
    @Service 
    public class B { 
        // 对实例A进行一个懒加载 
        @Lazy 
        @Autowired 
        private A a; 
    } 
    

10. @Async与@Transactional对循环依赖的影响

今天写项目时连用了@Async和@Transactional,产生了循环依赖。代码如下:

public class JmlRiskRuleServiceImpl extends ServiceImpl<JmlRiskRuleMapper, JmlRiskRule> implements IJmlRiskRuleService {
    private static final Logger logger = LoggerFactory.getLogger(JmlRiskRuleServiceImpl.class);

    @Resource
    private IJmlRiskRuleService iJmlRiskRuleService;

    @Override
    @Async
    @Transactional
    public void scanRiskHandle(HttpServletRequest request, JmlMpWechatuser wxUser, String projectCode, String qrcode) {
        try {
            ...
            iJmlRiskRuleService.save();
        }catch (Exception e){
            logger.info("扫码风控异常", e);
        }
    }
}

启动项目时报错:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'jmlRiskRuleServiceImpl': Bean with name 'jmlRiskRuleServiceImpl' has been injected into other beans [jmlRiskRuleServiceImpl] 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.

一开始以为是 @Async与@Transactional连用导致了循环,但是其实不是,这两个注解根本就不会有冲突。

@Async的作用是开启一个新线程,主线程(调用线程)与从线程(被调用线程)之间是完全隔离的,注意,是完全隔离,线程隔离!
@Transactional的作用是mysql的事务控制。

springboot中的各种功能注解都是用aop进行实现的,核心都是靠着关键的MethodInterceptor实现,这两个注解也不例外,@Async会给对应bean代理对象中放入一个AnnotationAsyncExecutionInterceptor拦截器,而@Transactional会给对应bean的代理对象中放入一个TransactionInterceptor拦截器。并且拦截器是有优先级的,无论什么时候@Async注解提供的拦截器优先级都是最高,因为它的作用是开启一个新线程,并且是完全隔离的线程。
所以以上代码之所以产生循环依赖,完全是因为自依赖的情况下使用了@Async,跟@Transactional完全无关, 一般来说使用@Async时往往会产生springboot无法自解决的循环依赖,因为线程的产生比较复杂。

以上其实仍然是@Async对循环依赖的影响。

在此再说一下,@Async与@Transactional连用产生的影响。

@Async注解的作用是开启一个新线程,主线程与异步线程之间完全隔离,完全隔离的意义是完全不影响,事务@Transactional只限制在线程之内。


@Async
@Transactional
A

@Async
@Transactional
B

调用时 A.B  --》 此时,a和b完全是两个线程,自然也是两个事务,此时不会遵从Transactional的定义合并事务。因为本质是两个线程。

@Async注解的方法上,再标注@Transactional注解,事务依旧是生效的,但生效范围只是本线程。

  1. 是否是一个事务,不能只看注解,还要看是否在一个线程,也就是看有没有调用方法中有没有@Async注解修饰
  2. 不同线程之间的事务完全隔离,换句话说,事务局限于线程之内
  3. 异步方法内仍是可以调用异步方法的,一个@Async注解便会开启一个新线程。

你可能感兴趣的:(spring系列,spring,boot,java,spring)