四Spring框架-循环依赖及缓存的解决

四Spring框架-循环依赖及缓存的解决

4.1 spring解决循环依赖以及缓存问题

spring创建和实例化bean的过程的环节是比较多并且包装比较深的,那么如果每次getBean时都需要走这么多环节的话,那么不但会产生很多内存对象和计算逻辑,而且更重要的是无法解决对象在一些场景中的依赖问题,尤其是循环依赖的问题。因此spring本身也考虑到了这个问题,在创建bean的过程中会有一些相关的缓存设计。今天我们就一起来看一下它是如何用缓存来解决的。

spring用三级缓存来管理

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

     //一级缓存,缓存创建单例完成且完成DI依赖注入的完整的bean
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	//二级缓存,映射bean的早期引用,该map内的bean尚未完成属性的注入,不是完整的bean
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  
     //三级缓存,映射创建bean的原始工厂ObjectFactory(当搜索二级没有且允许提前expose时,用三级)
     //ObjectFactory实现getObject方法,自定义获取bean 
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

三级缓存解决如下场景的使用

4.1.1 缓存场景:首次getBean

首次getBean,是从applicationContext中获取bean时,首次调用getBean

image-20220408141045850

首次获取bean时,根据上图的流程,先从一级或二级缓存中获取,若拿到则直接返回。若没有则直接从三级缓存中拿,当三级缓存中有缓存对象时,则通过缓存的beanFactory .getObject()直接拿到bean, 同时移除三级缓存将拿到的bean写入二级缓存中,然后返回对象。

AbstractBeanFactory

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {


image-20220408140617184

DefaultSingletonBeanRegistry.getSingleton(string,boolean allowEarlyReference)

image-20220408140659007

4.1.2 缓存场景:首次createBean

image-20230310111324354

DefaultSingletonBeanRegistry
//根据beanname返回注册的单例对象;如果没有注册,就创建并注册一个新的对象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "'beanName' must not be null");
		synchronized (this.singletonObjects) {
      //从一级缓存中获取
			Object singletonObject = this.singletonObjects.get(beanName);
      		//当一级缓存中没有对象时
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while the singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
        //在创建之前,把当前beanname加入this.singletonsCurrentlyInCreation正在创建中
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<Exception>();
				}
				try {
          //用传入的singletonFactory的getObject方法,获取对象
          //创建bean的逻辑
					singletonObject = singletonFactory.getObject();
					newSingleton = true;  //设置标志位-新单例对象
				}
				catch (IllegalStateException ex) {
					//如果执行这里,表示bean已经隐式的创建成功,直接从一级缓存获取对象返回
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
          //在singleton创建之后,this.singletonsCurrentlyInCreation.remove(beanName),从正在创建表中删除name
					afterSingletonCreation(beanName);
				}
        //如果singletonObject是从objectFactory.getobject创建
				if (newSingleton) {
          //添加新创建的singletonObject到一级缓存,同时删除二级、三级缓存内容
					addSingleton(beanName, singletonObject);
				}
			}
			return (singletonObject != NULL_OBJECT ? singletonObject : null);
		}
	}

在doCreateBean()过程中,若bean设置可提前暴露(默认开启),则会创建三级缓存(beanFactory对象)

创建三级缓存的条件,是允许singleton提前暴露,条件是mbd是单例的&允许循环依赖(引用)&正在创建中。

image-20230310111353256

为什么会在创建三级缓存时,同时移除二级缓存?因为二级缓存中的对象是从三级缓存中获取的,所以当三级缓存更新时会同时移除老旧的二级缓存数据,避免产生缓存数据不一致问题。

4.1.3 缓存依赖

在A对象中,持有对B对象的引用;同理在B对象中,也支持对A对象的引用。这种循环依赖分两种情况:1.在A对象中,B对象作为A对象的成员变量;2.在A对象中,B对象作为A对象的构造参数依赖, 这两种方式一旦有相互成环的引用场景,就需要引起重视了。

image-20220408142719535

以上截图属第一种情况,spring也只有对这种情况有考虑。这种情况spring视为正常引用,其它情况spring不支持且有此情况会直接抛出异常。

image-20220408142743454

上述方法,主要通过判断当前beanname是否是正在创建中——prototypeCurrentlyInCreation。

解决方案

那么对于以上的第一种循环依赖情况,spring是如何解决的呢?通过构建三级缓存机制完美解决这个问题。具体的解决过程请看下面的流程图

image-20230310111409349

根据上面的解决流程图和贴出的缓存源码, 详细的解释如下:

  1. 当CircularRefA实例化时,先从缓存种拿实例bean

  2. 当三个级别的缓存中都不存在对象时调用getSingleton(beanName,ObjectFactory)创建实例;

  3. 调用匿名类创建实例createBean();

  4. 通过构造函数实例化CircularRefA对象(对象中的依赖属性和成员没有被实例化,也就是CircularRefB对象这时是null);

  5. 创建第三级缓存,缓存CircularRefA对应的objectFactory对象;----------------重要点

  6. 对CircularRefA进行依赖注入,这时触发CircularRefB对象getBean()操作;

  7. 程序递归进入步骤1…步骤6;

  8. 当同理到步骤6时,在CircularRefB中对CircularRefA进行依赖注入, 触发getBean()操作;

  9. 这时从第三级缓存中可以拿到CircularRefA的引用(虽然只是个骨架),于是CircularRefA被注入;

  10. 紧接着CircularRefB实例化完成,然后通过依赖注入赋值给CircularRefA中的CircularRefB成员变量;

  11. 最终CircularRefA和CircularRefB两个对象都实例化完成(他们彼此持有对方的引用,这时也会被赋值上);

    最后要明确一个结论:只有beandefintion的Scope=Singleton类型才能进行缓存和支持以上的第一种情况的循环依赖,其它类型的只要出现循环依赖,spring直接抛出异常。至于其它情况为什么spring不支持大家看过源码,应该或多或少能想得到。

posted @ 2023-03-10 17:08  LeasonXue  阅读(108)  评论(0编辑  收藏  举报