Spring循环依赖源码解析

Spring 循环依赖源码解析

文章目录

    • Spring 循环依赖源码解析
      • Bean 生命周期简述
      • 循环依赖产生时机
      • 解决思路
        • 引出二级缓存
      • AOP 代理问题
        • AOP 解决思路
        • 引出三级缓存
        • 重复 AOP 产生代理对象问题
      • 流程总结
      • 三级缓存意义总结
        • 第三级缓存的必要性
      • 循环依赖场景
        • 多例 Bean 注入
        • 构造器注入
        • @Async 异步调用

Bean 生命周期简述

  1. 扫描 bean
  2. 遍历 bean 实例化
  3. bean 依赖注入
  4. 初始化前
  5. 初始化
  6. 初始化后
  7. 完成bean创建,注入单例池
  8. bean销毁

循环依赖产生时机

循环依赖产生处于依赖注入这阶段,例:BeanABeanB直接存在循环依赖:

  1. 实例化BeanA,给BeanA进行依赖注入
  2. BeanA中存在BeanB,单例池中不存在BeanB,需要创建BeanB
  3. BeanB实例化后,给BeanB进行依赖注入
  4. BeanB中存在BeanA,单例池中不存在BeanA,需要创建BeanA
  5. 陷入死循环

Spring循环依赖源码解析_第1张图片

解决思路

由上逻辑可以发现当 BeanA注入BeanB时,BeanB需要注入BeanA时由于BeanA已经被实例化了(虽然属性不完整),那么就可以给BeanB先注入这个早期的BeanA,那么BeanB就可以继续后续环节,完成后BeanA也能继续后续环节,这时的BeanA就是一个完善的 bean 了,由于这个beanBeanB中的是同一个对象,那么BeanB中的BeanA也是一个完整的bean,这样就解决了循环依赖的问题。

引出二级缓存

经过上面分析,可以看出我们需要一个能得到 bean 实例化后的早期对象的地方,而 Spring 中就提供了 Map earlySingletonObjects(key:beanNanme,value:早期bean对象) 来存储早期 bean

Spring循环依赖源码解析_第2张图片

AOP 代理问题

经过上面分析似乎解决了循环依赖问题,但是当BeanA需要 AOP 生成代理对象时就会出现问题:
BeanB中存储的早期BeanA是原始对象,那么到最后就会出现BeanB中的BeanA是一个原始对象而不是代理对象问题。

AOP 解决思路

为了解决上面问题,可以发现:如果能根据这个bean是否出现循环依赖而提前对bean进行AOP从而生成其代理对象存储,之后也BeanB中也直接利用这个代理对象BeanA就好了。

抛出问题:什么时候能最方便判断这个bean是否被循环依赖了呢?

分析可以得到当在创建BeanB,给BeanB进行依赖注入时是可以很方便判断的,而Spring也正提供了一个Set singletonsCurrentlyInCreation 来存储当前正在创建中的bean,通过这个就可以在给BeanB注入BeanA时从这个Set中判断BeanA是否存在而知道是否出现了循环依赖。然后就可以判断是否需要进行AOP生成代理对象,为BeanB注入对应的代理对象/原始对象。

引出三级缓存

根据上面可以发现,无论是否需要生成代理对象,都需要用到原始对象(代理对象是基于原始对象生成的,而不需要生成代理对象也需要保证原始对象的单例),所以这里我们需要能根据注入的beanName获取到原始对象,那么可以利用一个 Map

Spring 提供了一个 Map> singletonFactories达到上述功能。MapkeybeanNamevalueObjectFactory(一个函数式接口)Spring传入的value一个 Lambda 表达式,逻辑是判断该bean是否需要进行AOP需要则创建,不需要返回原始对象。
这样做的话就可以在实例化bean后就存入这个Map中,在属性注入时从这个Map中取出value执行Lambda方法得到对应的对象。

// 实例化后置处理RootBeanDefinition后

// 是否单例 && 是否允许循环依赖 && 该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");
    }
    // 循环依赖-添加到三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
重复 AOP 产生代理对象问题

上面逻辑会存在一个问题:提前进行了AOP,那么是否会在初始化后重复进行AOP

点进存入三级缓存的Lambda表达式方法:

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

可以看到处理的方法是 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {


    @Nullable
    default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }


    @Nullable
    default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
        throws BeansException {

        return null;
    }


    default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

而这个接口的 getEarlyBeanReference() 方法只有一个实现类 AbstractAutoProxyCreator

// 重点属性:存储早期代理对象;key:beanName/Class,value:代理对象
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);

// 两个主要的方法

/**
 * 初始化后方法(正常执行AOP的方法)
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 早期AOP集合中的bean != 传入bean(即未被代理),并移除
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

/**
 * 提前进行AOP调用方法
 */
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 将这个bean缓存到早期AOP集合
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
}

看到上面可以得知是如何避免二次AOP的,顺便看下创建代理的方法wrapIfNecessary

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

    // advisedBeans表示已经判断过了的bean,false表示此bean不需要进行Aop
    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;
}

流程总结

  1. bean实例化后置处理RootBeanDefinition后,Spring会将信息存入三级缓存Map> singletonFactories

    // 实例化后置处理RootBeanDefinition后
    
    // 是否单例 && 是否允许循环依赖 && 该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");
        }
        // 循环依赖-添加到三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    
  2. 进行依赖注入

  3. 对于bean属性进行getBean(),走到doGetBean()–>getSingleton()

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 去单例池获取 bean
        Object singletonObject = this.singletonObjects.get(beanName);
        // 单例池不存在 && 这个bean正在创建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            // 去二级缓存(早期bean单例池)取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 不存在 && 允许循环依赖
            if (singletonObject == null && allowEarlyReference) {
                // 加锁考虑的是多线程的问题
                // todo:在5.3.10之前是单例池未获取到对象就加锁,这里为了降低锁的粒度进行了改变
                //  	但是引发一个问题,就是当在将三级缓存生成的对象放入二级缓存后,此时失去CPU调度,另一个线程进入这个方法的开头会判断二级缓存存在直接返回
                //		然而此时的bean并未完全创建成功导致获取的对象是个早期对象就会出现后进入的线程得到了对象但是属性值没注入成功
                synchronized (this.singletonObjects) {
                    // 再次去单例池获取(在这里可能有其他线程的变动使对象被加入)
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        // 单例池和早期单例池都不存在
                        if (singletonObject == null) {
                            // 去三级缓存查找,Map
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                // 三级缓存存在,则调用判断方法生成对应的对象(可能是代理/原始对象)
                                singletonObject = singletonFactory.getObject();
                                // 将生成的对象存入到二级缓存(早期单例池)
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                // 从三级缓存移除
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }
    
  4. 发生循环依赖注入早期对象(代理/原始),并将第三级缓存中的对象移除,进行后续操作完成bean创建

  5. 完成外层对象的创建,将第二级缓存对象移除,第一级缓存添加完整对象

三级缓存意义总结

  • 一级缓存 singletonObjects
    即单例池中对象,缓存的是已经经历了完整生命周期的 bean 对象
  • 二级缓存 earlySingletonObjects
    缓存的是早期的 bean(代理/原始) 对象,是Bean的生命周期未完整经历的对象
    解决了循环依赖过程的循环注入问题,使得可以注入早期对象
  • 三级缓存 singletonFactories
    缓存的是ObjectFactory(函数式接口),表示对象工厂,用来创建早期bean对象的工厂
    解决了创建AOP需要用到原始对象,且保证原始对象单例问题
  • 重复AOP判断缓存 earlyProxyReferences
    它用来记录某个原始对象是否进行过AOP
第三级缓存的必要性

如果不用第三极缓存也是可以达到相同的结果,例:

  1. 上述BeanB注入BeanA时可以在后续方法中都增加传参原始对象bean来解决问题
    不过这显然并不合适,且也同样需要对防止二次AOP逻辑进行修改
  2. 在实例化后就进行 AOP 生成代理对象并缓存到二级缓存earlySingletonObjects
    不过这样不仅违背了Spring Bean生命周期的流程,也让代码显得臃肿

循环依赖场景

多例 Bean 注入

对于多例Bean每次获取都是要进行创建bean的,是不存在其实例被缓存情况。那么如果多例bean之间存在循环依赖就会出现反复创建的死循环,所以多例bean的循环依赖Spring并不能解决。

构造器注入

构造器注入是在实例化前就需要用到构造器参数,但是由于bean还未实例化,所以也无法缓存得到早期对象,且Spring建议使用构造器注入,并认为出现循环依赖是不规范的,构造器注入能及时发现问题。

不过如果一定要解决,可以配合使用 @Lazy 注解在指定构造器上,这样表明构造器需要的参数是懒加载的,就会先创建参数的代理对象供构造器使用。

@Async 异步调用

@Async 的实现是利用了 BeanPostProcesser。一个bean中使用了@Async,则会在初始化后阶段多执行一个用来处理@AsyncBeanPostProcesser;而这个实现也是生成一个代理对象,那么就会出现循环依赖中两个bean对象的不一致性(依赖注入的是通过三级缓存生成的代理对象,自己本身会多执行处理@AsyncBeanPostProcesser生成新的代理),从而抛出异常。

你可能感兴趣的:(spring,java,后端)