Spring在创建Bean实例的时候,有时避免不了我们编写的Java类存在互相依赖的情况,如果Spring对这种互相依赖的情况不做处理,那么就会产生创建Bean实例的死循环问题,所以Spring对于这种情况必须特殊处理,下面就来探究Spring是如何巧妙处理Bean之间的循环依赖问题

1 暴露钩子方法getEarlyBeanReference

首先对于单实例类型的Bean来说,Spring在创建Bean的时候,会提前暴露一个钩子方法来获取这个正在创建中的Bean的地址引用,其代码如下:

springboot 两个bean 相互依赖 spring相互依赖怎么解决_spring

springboot 两个bean 相互依赖 spring相互依赖怎么解决_1024程序员节_02

如上面的代码所示,此时会在singletonFactories这个Map中提前储存这个钩子方法singletonFactory,从而能够提前对外暴露这个Bean的地址引用,那么为什么获取地址引用需要包装成复杂的方法呢?下面会解释

2 其他Bean获取提前暴露的Bean的地址引用

当其他Bean需要依赖正在创建中的Bean的时候,就会调用getSingleton方法来获取需要的Bean的地址引用

springboot 两个bean 相互依赖 spring相互依赖怎么解决_spring boot_03

如上诉代码所示,在获取Bean的时候会从三个地方来获取

1:singletonObjects :这个是存放已经完全创建完成的Bean实例的Map

2:earlySingletonObjects :这个是存放用提前暴露的钩子方法创建好的Bean实例的Map

3:singletonFactories :这个是用来存放钩子方法的Map

当获取依赖的Bean的时候,就会调用钩子方法getEarlyBeanReference来获取提前暴露的Bean的引用,这个方法的源码如下:

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

 如上面的源码所示,这个方法主要是需要调用SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference方法来提前处理一下尚未创建完成的Bean,而getEarlyBeanReference方法有逻辑的实现类只有一个org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator,这个类就是创建Aop代理的类,其代码如下:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    //提前标记这个bean已经创建过代理对象了
    this.earlyProxyReferences.put(cacheKey, bean);
    //按条件创建代理对象
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}

 如上面的代码所示,这段代码的主要目标就是判断提前暴露的Bean是否需要做动态代理,需要的话就会返回提前暴露的Bean的动态代理对象

那么这里为什么要去判断是否需要动态代理呢?考虑下面这种情况

1:如果这里不返回这个Bean的动态代理对象,但是这个Bean在后续的初始化流程中会存在动态代理:

举例:这里假设A依赖B,B又依赖A,此时B正在获取A提前暴露的引用,如果这时将A本身的地址引用返回给B,那么B里面就会保存A原始的地址引用,当B创建完成后,程序返回去创建A时,结果A在初始化的流程(initializingBean)中发生了动态代理,那么这时Spring容器中实际使用的是A的动态代理对象,而B却持有了原始A的引用,那么这时容器中就会存在A原始的引用以及A的动态代理的引用,从而产生歧义,这就是为什么需要提前去判断是否需要创建动态代理的原因,这个原因的问题在于填充属性populateBean流程在初始化流程(initializingBean)之前,而创建动态代理的过程在初始化流程中

3 判断Bean的地址是否发生变化

Spring在Bean初始化之后,又判断了一下Bean初始化之后的地址是否发生了变化,其代码逻辑如下所示:

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    //判断是否触发了提前创建bean的逻辑(getEarlyBeanReference)
    //如果有其他bean触发了提前创建bean的逻辑,那么这里就不为null
    if (earlySingletonReference != null) {
        //判断引用地址是否发生了变化
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
	}
    }
}

那么这里为什么需要在初始化之后继续判断Bean的地址是否发生了变化呢?

这是因为,如果存在循环依赖,同时Bean在初始化的流程(initializingBean)中又发生了额外的动态代理,例如,除了在getEarlyBeanReference中发生的动态代理之外,还有额外的动态代理发生了,也就是发生了两次动态代理,那么这时Bean的地址与getEarlyBeanReference流程中产生的Bean的地址就不一样了,这时如果不处理这种情况,又会出现Spring容器中同时存在两种不同的引用对象,又会造成歧义,所以Spring需要避免这种情况的存在

4 如果Bean地址发生变化则判断是否存在强依赖的Bean

Spring在Bean的创建过程中如果出现了上诉6.3节的情况时,Spring采取了下面的方法进行处理:

else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    //获取该Bean依赖的Bean
    String[] dependentBeans = getDependentBeans(beanName);
    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    //去除因为类型检查而创建的Bean(doGetBean方法typeCheckOnly参数来控制)
    for (String dependentBean : dependentBeans) {
        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
	    actualDependentBeans.add(dependentBean);
        }
    }
    //如果去除因为类型检查而创建的bean之外还存在依赖的bean
    //(这些剩下的bean就是spring实际需要使用的)那么就会抛出异常,阻止问题出现
    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.");
    }
}

上面这段代码就是Spring处理上诉情况的逻辑,首先明确的是Spring不允许上诉情况发生,Spring对于Bean的引用地址发生变化的情况,Spring首先会判断依赖的Bean是否已经完全创建完毕,如果存在完全创建完成的Bean就会直接抛出异常,因为这些完全创建完成的依赖Bean中持有的引用已经是旧地址引用了

具体的处理逻辑是:先拿到该Bean所有依赖的Bean,然后从这些Bean中排除仅仅是因为类型检查而创建的Bean,如果排除这些Bean之后,还有依赖的Bean,那么这些Bean就是可能存在循环依赖并且是强依赖的Bean(这些Bean中持有的引用地址是老地址,所以会存在问题),Spring就会及时抛出异常,避免发生问题