当前位置: 移动技术网 > IT编程>开发语言>Java > 为什么spring单例要使用三级缓存

为什么spring单例要使用三级缓存

2020年07月27日  | 移动技术网IT编程  | 我要评论

熟悉spring框架的同学应该都知道spring单例使用的三级缓存,简单回顾下哪三级缓存,源码类:DefaultSingletonBeanRegistry

  1. 一级缓存:singletonObjects
  2. 二级缓存:earlySingletonObjects
  3. 三级缓存:singletonFactories

直接使用一级缓存不可以吗?

一级缓存,也就是直接将单例bean缓存至singletonObjects,去除其他缓存。设想下面这个场景

  1. 创建单例A
  2. 初始化单例A(单例A强依赖单例B),注入单例B
  3. 从工厂中获取单例B发现不存在,则创建
  4. 创建单例B
  5. 初始化单例B(单例B强依赖单例A),注入单例A
  6. 从工厂中获取单例A发现不存在(因为正在创建中),则创建??????

没错,问题来了,循环依赖,死循环,直接凉凉。

直接使用二级缓存不可以吗?

对于直接使用一级缓存的问题已经暴露,如何解决?那么有同学就要问了,那我在创建单例A时就直接将单例A放入缓存中不可以吗?这样不就解决了循环依赖问题?是的,这样处理是可以的。但是我们如何判断从工厂中获取的一个单例初始化完成了?对单例的所有依赖注入的属性进行一次判空来判断?依赖注入的实例是否完成了初始化?使用工厂中的单例还要各种判空是不是很恶心?增加二级缓存岂不快哉
使用二级缓存:earlySingletonObjects+singletonObjects

  1. 创建单例A
  2. 单例A放入二级缓存:earlySingletonObjects
  3. 初始化单例A(单例A强依赖单例B),注入单例B
  4. 从工厂中获取单例B发现不存在,则创建
  5. 创建单例B
  6. 单例B放入二级缓存:earlySingletonObjects
  7. 初始化单例B(单例B强依赖单例A),注入单例A
  8. 从一级缓存singletonObjects获取单例A,存在则返回,不存在继续查询二级缓存:earlySingletonObjects,如果不存在则创建单例A(此时发现循环依赖抛出异常),但是看到前面已经缓存在二级缓存中
  9. 注入单例A(可能是未完成初始化的单例A)完成
  10. 初始化单例B完成,移除二级缓存中的单例B,缓存单例B至一级缓存,返回单例B
  11. 注入单例B完成
  12. 初始化单例A完成,移除二级缓存中的单例A,缓存单例A至一级缓存,返回单例A

可以看到我们之前的问题全部迎刃而解,只要从工厂中的一级缓存中获取的单例均是完成了初始化,并且依赖注入的实例也均是完成了初始化,只是如果是循环依赖场景,可能得到的依赖注入时未完成初始化的,即某些依赖注入属性可能为null,例如:@Value注入的一些值。

为什么spring单例要使用三级缓存?

那么二级缓存已经解决了问题,为什么还要引入三级缓存呢?设想下,如果我想要在读取二级缓存:earlySingletonObjects对象时统一增加一些日志或者其他处理动作,如何解决?在读取earlySingletonObjects对象实例后,对实例进行回调某些接口方法?当然没问题,可以看到spring对于非单例bean也采用该方法来处理前置、后置动作的回调。

else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		afterPrototypeCreation(beanName);
	}
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// getNonSingletonFactoryBeanForTypeCheck方法中也有同样对prototype类型bean的前后置回调处理代码

但是每次修改回调动作都要对所有读取earlySingletonObjects对象的地方进行修改,岂不是很恶心?每次增加一个读取earlySingletonObjects对象的动作都要记得加入相应的回调。是不是很容器遗漏和出错?那么如果我们缓存的是一个接口而不是一个单例对象问题是不是就解决了呢?只需要对接口增加对应的动作即可,例如:

  • 合并bean定义后回调:MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition
  • 自定义决定使用哪个构造器构造bean:SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors
  • 创建bean前的属性值处理回调:InstantiationAwareBeanPostProcessor.postProcessPropertyValues
  • 创建bean前的处理回调:InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
  • 创建bean后的处理回调:InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation
  • 二级缓存bean的处理回调:SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference
  • 断言特殊bean的bean类型,例如FactoryBean:SmartInstantiationAwareBeanPostProcessor.predictBeanType

对于循环依赖注入的未完成初始化的单例,如果有特殊需求,期望保证其一些属性的提前注入,可以通过该扩展对提前曝光的对象进行一些个性化定制的处理,例如:将@Value的值提前从Environment中获取并注入到提前曝光的对象。而不需要修改框架代码,只需要增加对应的接口实现即可

总结

  1. 循环依赖已经完全解决了吗?并没有,对于构造器注入的循环依赖时无法解决的,这个是无解的#-_-!!!
  2. 二级缓存是为了解决属性字段级别的循环依赖注入问题
  3. 三级缓存是为了增强框架的扩展性
  4. 对于非单例模式{@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)}的bean循环依赖问题依然存在,会抛出循环依赖异常
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; 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?

本文地址:https://blog.csdn.net/u010597819/article/details/107595103

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网