Spring循环依赖
前言
不知道大家听过Spring的循环依赖这个问题吗,而且这个问题是面试经常问的,属于Spring的一个比较重要的话题,也比较典型,比较考验一个人对Spring的研究程度
循环依赖问题,本文会通过三个方面来简单介绍
1、什么是Spring的循环依赖
2、多种情况下的循环依赖
3、Spring如何解决循环依赖
了解Spring循环依赖
什么是循环依赖
@Component
public class AService {
// AService中注入了BService
@Autowired
private BService bService;
}
@Component
public class BService {
// BService中也注入了AService
@Autowired
private AService aService;
}
这是属于比较常见的一种循环依赖,还有就是更多的之间的相互依赖,比如A依赖B,B依赖C,C依赖A,类似于三角恋…
当然也有特殊的,自己的依赖自己
// 自己依赖自己
@Component
public class AService {
// A中注入了A
@Autowired
private AService aService;
}
关于上面service的Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化
我们如果用一个常人的思维去考虑,这肯定是做不到的啊,A需要B,B需要A,这不成死循环了,和死锁一个道理了,这就很尴尬了,该怎么解决呢,接着看下去
多种情况下的循环依赖
Spring的循环依赖也是可能出现多种情况的,比如构造器注入,setter注入等等,那什么情况下Spring可以解决循环依赖,什么情况下又不能解决呢
Spring解决循环依赖的前提条件就是:
1、出现循环依赖的bean必须是单例的
2、依赖注入的方式不能全是构造器注入
注意不能全是这几个字眼,这里需要强调一点的是,大家可能会看到很多关于Spring解决循环依赖的博客,其中只能解决setter注入的方式这种说法是错误的,只要不全是构造器注入Spring就可以解决
Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化,在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过后置处理器设置,知道了这一点,可以更好的帮助大家去理解
上面说的第一点必须是单例的其实很好理解,你想啊,如果是多个实例,该引入哪个实例就不知道了,Spring框架就蒙圈了(spring框架默认单例)
但是第二点呢,不能全是构造器注入呢,先看代码
@Component
public class AService {
// @Autowired
// private BService bService;
public AService(BService bService) {
}
}
@Component
public class BService {
// @Autowired
// private AService aService;
public BService(AService aService){
}
}
上面这个例子大家看得懂吧,别告诉我你看不懂,AService中的BService注入是通过构造器,反之也是通过构造器注入,这个时候Spring是无法解决循环依赖问题的,如果项目中出现两个这样的引用,在启动的时候就会直接抛出异常
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
解决循环依赖
首先呢,Spring解决循环依赖依靠的就是内部维护的三个Map,也就是咱们常说的三级缓存,不知道大家听过没有
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
之所以被叫做三级缓存,大概是因为注释上都是用Cache,而起的作用也类似一个缓存的作用
1、singletonObjects:存放已经经历了完整生命周期的Bean对象 成员属性都是有值的
2、earlySingletonObjects:存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
3、singletonFactories:存放可以生成Bean的工厂
二缓和三缓属于消耗的过程, 为了创造完整的单例bean, 暂时缓存在这里, 过后就清除掉了(不同于常规缓存理解, 一般有更新时候, 各级缓存同步, 这里是删除)
1、A找B找不到:
创建AService的过程中发现需要BService,于是AService将去寻找BService,发现找不到,寻找的路径是一级缓存、二级缓存、三级缓存,于是AService把自己放到了三级缓存中
2、B找A找到了:
实例化BService,实例化过程中发现需要AService,于是也是按照上述路径去寻找,在三级缓存中找到了AService
3、B创建完成:
然后就把三级缓存中的AService拿出来,放到了二级缓存中,并删除三级缓存中的AService,此时BService可以成功引用,顺利初始化完毕,把自己放到了一级缓存中了(而此时BService中的AService依然是创建中的状态
4、A创建完成:
继续完善AService,此时再去寻找BService,拿出来直接引用就好了,把自己放入到一级缓存中,删除二级缓存,删除在创建的状态信息
来一起简单的分析一波源码,不看太多,不用发愁
1、先将AService封装成ObjectFactory, 存入三级缓存中
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));
}
执行Lambda 执行该方法:getEarlyBeanReference
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;
}
将getEarlyBeanReference生成的结果存入三级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
2、AService中属性赋值,先实例化BService, BService又依赖AService
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//根据beanName 从单例池中获取bean对象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从二级缓存中获取bean对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取singletonFactory
ObjectFactory<?> singletonFactory = this.singletonFactories
.get(beanName);
if (singletonFactory != null) {
##执行三级缓存对应的..执行Lambda 执行该方法:
getEarlyBeanReference
// 判断如果开启了aop 则会生成代理类 AService代理类
// 如果没有开启aop的情况下 则直接返回原始不完整bean对象
singletonObject = singletonFactory.getObject();
//将该代理类存入到二级缓存中 存入不完整对象
this.earlySingletonObjects.put(beanName, singletonObject);
//在删除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
说明: 可以看到, 在获取Aservice过程中, 依次从一二三级缓存中获取, 执行三级缓存的lumuda表达式, 将生成的bean存入二缓, 并删除三缓, Bservice从二缓找到Aservice, 实例化完成, 存入一缓
执行lumuda表达式的实际作用是为了判断返回的具体哪种对象
- 如果我们开启了aop的情况下 则返回aop代理对象
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
-
如果我们没有开启aop的情况下 则返回原始对象
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
spring已经帮我们解决了循环依赖问题, 在实际开发中 注意写代码规范, 避免出现
1. 自己依赖自己
2. 开启了多例模式
3. AB同时使用构造注入
在排除以上情况, 如果di属性注入出现循环依赖, 使用@lazy解决即可