浅谈Spring之循环依赖问题剖析

浅谈Spring之循环依赖问题剖析

Tans 1,868 2022-03-23

循环依赖

1.何为循环依赖

首先,何为循环依赖?这种情况发生在多个对象存在互相环状依赖关系的时候,最简单的情况:当A依赖于B,B依赖于A。

class A{
     public B b; //A依赖于B
}
class B{
     public A a; //B依赖于A
}

上述例子,其实如果我们不使用Spring的话,很容易解决:

A a = new A();
B b = new B();
a.b=b;
b.a=a;

但是在Spring自动框架中,如何来解决我们的循环依赖问题呢?

2. Spring中发生了什么?

上述例子,在Spring中,对象不再受人工控制,而是由IOC容器来控制,其每个对象都有其生命周期,正是生命周期的存在出现了循环依赖问题。因为在Bean的生命周期中,有一个重要的步骤是属性填充

如果不发生依赖的话,假设我们的依赖是这样的 : Bean A --> Bean B --> Bean CSpring首先会创建 bean C, 然后创建bean B(将bean C注入到其中),最后创建bean A(将bean B注入到其中)。

而如果发生循环依赖的时候,Spring就会不知道先去创建哪个bean,因此就会发生循环依赖注入错误,抛出org.springframework.beans.factory.BeanCurrentlyInCreationException

Example:测试(所有类文件都在circularDependencies包目录下)

  1. 首先我们创建两个互相依赖的对象

    @Component
    public class A {
        public B b;
        @Autowired
        public A(B b){this.b = b;}
    }
    
    @Component
    public class B {
        public A a;
        @Autowired
        public B(A a){this.a = a;}
    }
    
  2. 创建配置类

    @Configuration
    @ComponentScan(basePackages = {"circularDependencies"})
    public class TesConfig {
         
    }
    
  3. 创建测试类

    public class Test {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TesConfig.class);
        }
    }
    
  4. 测试结果

     Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    

3. 什么情况下可以循环依赖可以解决?

Spring解决循环依赖是有前置条件的:

  • 出现循环依赖的Bean必须是要单例的
  • 依赖注入的方式不能全是构造器注入的方式

对于上述例子,我们可以测试不同的注入方式来查看是否解决循环依赖注入问题。

依赖情况 依赖注入方式 循环依赖是否解决
AB相互依赖(循环依赖) 均采用setter方式注入
AB相互依赖(循环依赖) 均采用构造器方式注入
AB相互依赖(循环依赖) A中注入B的方式为setter方法,B中注入A的方式为构造器
AB相互依赖(循环依赖) B中注入A的方式为setter方法,A中注入B的方式为构造器

4. Spring是如何解决循环依赖问题的?

简单的无AOP依赖

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TesConfig.class);

上述代码中间发生了什么?

上图是获取以及创建Bean a的整个调用链。首先,我们先去获取Get这个bean,会发现没有找到,然后去Create这个bean,在创建过程中又主要分为实例化,属性注入初始化几步操作,然后才能使用。我们来顺着调用链来捋一捋:

protected <T> T doGetBean(
     String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) {
     String beanName = transformedBeanName(name);
     Object beanInstance;
     //7. getBean(b)开始执行
     //8. getBean(a)开始执行
    //首先直接获取bean对象,此处注意调用getSingleton的单参数重载方法。看下一个代码块。
     Object sharedInstance = getSingleton(beanName);
     if (sharedInstance != null && args == null) //.......
     //找不到我们想要的bean,那么调用另一个 getSingleton的第二个重载方法
     else {
          if (mbd.isSingleton()) {
               //getSingleton(String beanName, ObjectFactory of)
               //1. 构造一个匿名内部类然后去创建
               sharedInstance = getSingleton(beanName, () -> {
                         return createBean(beanName, mbd, args);
               });
               beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
          }
     }
    return adaptBeanInstance(name, beanInstance, requiredType);
}
public class DefaultSingletonBeanRegistry {
     
     /** 一级缓存,单例池 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
     
     /** 二级缓存,早期曝光对象 */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

	/** 三级缓存,早期曝光对象工厂*/
	private final Map<String, ObjectFactory<?>> 
          = new HashMap<>(16);


     //上述代码首先进入到此方法
     public Object getSingleton(String beanName) {
          return getSingleton(beanName, true);
     }
     @Nullable
     protected Object getSingleton(String beanName, boolean allowEarlyReference) {
          // Quick check for existing instance without full singleton lock
          Object singletonObject = this.singletonObjects.get(beanName); //首先从一级缓存中获取
          if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
               singletonObject = this.earlySingletonObjects.get(beanName);//一级缓存没有,从二级缓存中获取
               //这里使用了双重校验锁DCL的机制获取单例对象
               if (singletonObject == null && allowEarlyReference) {
                    synchronized (this.singletonObjects) {
                         // Consistent creation of early reference within full singleton lock
                         singletonObject = this.singletonObjects.get(beanName);
                         if (singletonObject == null) {
                              singletonObject = this.earlySingletonObjects.get(beanName);
                              if (singletonObject == null) {
                                   //三级缓存中获取
                                   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
                                   if (singletonFactory != null) {
                                        //9.在三级缓存找到了相应的a工厂对象,调用方法获得代理对象
                                        //第二次Bean进来就是调用下面这条语句获取其a对象o或者a代理对象的
                                        singletonObject = singletonFactory.getObject();
                                        this.earlySingletonObjects.put(beanName, singletonObject); //放入二级缓存
                                        this.singletonFactories.remove(beanName);//从三级缓存中移除
                                   }
                              }
                         }
                    }
               }
          }
          return singletonObject;
     }
     
     
     //2.创建bean的时候进入到此方法,也就是第一次创建bean a的时候
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
                      // 标志着这个单例Bean正在创建
                      // 如果同一个单例Bean多次被创建,这里会抛出异常
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
           //3:这里我们就会调用我们传进来的匿名内部类然后调用其实现的create方法,然后去调用下个代码块doCreateBean()方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
                    //............
				finally {
					afterSingletonCreation(beanName);
				}
                   	 // 添加到一级缓存singletonObjects中,可以看到最终都会放入到一级缓存中来
				if (newSingleton) addSingleton(beanName, singletonObject);
                    
                      //	protected void addSingleton(String beanName, Object singletonObject) {
				//	synchronized (this.singletonObjects) {
                      //	      this.singletonObjects.put(beanName, singletonObject); 放入一级缓存
                      // 	this.singletonFactories.remove(beanName);  移除二级缓存
                      // 	this.earlySingletonObjects.remove(beanName); 移除三级缓存
                      //	     this.registeredSingletons.add(beanName);  注册中心放入一级缓存
                      //   }
		}
	}
			}
			return singletonObject;
		}
	}

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

     // 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) {
          //4. 这里调用了我们创建方法 转到下述代码
          //匿名内部类类型为ObjectFactory
          //lambda表达式是对ObjectFactory的getObject()的方法的实现
          //getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) : 这里返回aop代理过的对象
          addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
     }

     // Initialize the bean instance.
     Object exposedObject = bean;
     try {
          //6. Bean a 开始属性注入
          //8. Bean b 开始属性注入
          populateBean(beanName, mbd, instanceWrapper);
          //初始化操作
          exposedObject = initializeBean(beanName, exposedObject, mbd);
     }
     return exposedObject;
}
//10. 这里获取a的代理对象或者是原对象
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;
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
     synchronized (this.singletonObjects) {
          if (!this.singletonObjects.containsKey(beanName)) {
               this.singletonFactories.put(beanName, singletonFactory);
               this.earlySingletonObjects.remove(beanName);
               this.registeredSingletons.add(beanName);
          }
     }
}

可以看到,在三级缓存中添加了可以拿到代理对象的Objectfactory的工厂,然后bean a的处理流程结束,进入b的创建流程。当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring又会去getBean(b),然后反射调用setter方法完成属性注入。

因为B需要注入A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的getBean是为了创建Bean,而此时再调用getBean不是为了创建了,而是要从缓存中获取,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(a)的流程就是这样子:

image-20220322233044799

因此,流程大致如下:

image-20200706133018669

存在AOP依赖

如果A存在AOP代理,那么放入三级缓存中相应的beanFactory就会返回其相应的代理对象,这一点也就做提前暴露。因为在对B做依赖注入的时候,我们希望注入的是A的代理对象而不是其对象本身。

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

总体流程如下:

循环依赖

5. 几个问题

为什么需要三级缓存,直接将代理对象存入二级缓存不就好了吗?

这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。也就是说只有当产生循环依赖的时候才会调用getObject()方法并生成代理对象返回。如果我们使用二级缓存的话,那么在属性注入之前就需要完成AOP代理过程,这个违背了Bean生命周期的设计。所以说三级缓存的存在是为了预防循环依赖问题所加的一个补丁机制,不产生循环依赖那么bean的生命周期不会产生影响。

初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?

不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类**,内部都持有一个目标类的引用**,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化。

三级缓存会提高效率?

  1. 当不存在AOP代理的时候,那么只是把beanFactory放入三级缓存中。显而易见,这样不会带来效率上的提升。
  2. 当存在AOp代理的时候,会发现我们只是把提取bean代理对象的时间提前了,实际上并没有进行步骤上的减少。

因此,三级缓存的存在并不是所说的提升了效率。更像是给依赖注入问题提供了一层保障。在尽可能保证安全的同时也注重了效率问题,也顺应了Spring官方所提倡的生命周期等。

为什么构造器方法注入无法解决循环依赖

由于构造器注入实在实例化期间执行的,当实例化A的时候,就需要去实例化B对象,这样就无法对A进行实例化,最终产生了循环依赖问题。可以使用Lazy注解解决。这也解释了为什么在上述测试中,第四个测试无法解决循环依赖的问题

6. 参考资料

  1. 循环依赖
  2. 面试必杀技,讲一讲Spring中的循环依赖
  3. Circular Dependencies in Spring