Spring中的循环依赖
面试必备
循环依赖
Spring循环依赖
就是循环引用,比如两个bean A和B 两者之间相互引用对方,或者说两者相互持有对方的引用。最终形成一个环
。
场景分析
spring中bean的注入方式有两种:Setter
和Constractor
注入(还有一种,接口注入基本不怎么用了),另外,bean的作用于也有多种(5中)singleton、property、reuest、session、application。Spring也不能对以上每种情况都可以进行循环依赖的处理。具体来说,值对于单例的setter注入bean可以处理。
1.原型(Prototype)
不支持,直接抛出BeanCurrentlyInCreationException
,原因是 Spring
容器对原型作用域的 bean
是不进行缓存,因此无法提前暴露一个创建中的 bean
,所以也是无法解决这种情况的循环依赖。
2.构造器(Constractor)
同样不支持,也会抛出BeanCurrentlyInCreationException
,原因也很简单
3.单例setter
首先确定的是单例作用下的setter注入的循环依赖spring是可以已解决的
单例的特点
提前实例化 bean
,将有问题的配置问题提前暴露
将 bean
实例放入单例缓存 singletonFactories
中,当需要再次使用时,直接从缓存中取,加快了运行效率。
那么spring是如何解决的呢
源码解析
在类的加载中,核心方法 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
,在这一步中有对循环依赖的校验和处理。
跟进去方法能够发现,如果 bean
是单例,并且允许循环依赖,那么可以通过提前暴露一个单例工厂方法,从而使其他 bean
能引用到,最终解决循环依赖的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 protected Object doCreateBean (final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null ; if (mbd.isSingleton()) { instanceWrapper = this .factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null ) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException (mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed" , ex); } mbd.postProcessed = true ; } } 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)); } Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException ( mbd.getResourceDescription(), beanName, "Initialization of bean failed" , ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != null ) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this .allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet <>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } 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 " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example." ); } } } } try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException ( mbd.getResourceDescription(), beanName, "Invalid destruction signature" , ex); } return exposedObject; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { // 判断 singletonObjects 不存在 beanName if (!this.singletonObjects.containsKey(beanName)) { //放入 beanName -> beanFactory,到时在 getSingleton() //获取单例时,可直接获取创建对应 bean 的工厂,解决循环依赖 this.singletonFactories.put(beanName, singletonFactory); //从提前声明的缓存中移除,之前在 getSingleton() 放入的 this.earlySingletonObjects.remove(beanName); //往注册缓存中添加 beanName this.registeredSingletons.add(beanName); } } }
另外,Spring内部维护了三个Map ,也常被成为缓存
在DefaultSingletonBeanRegistry
类中:
1 2 3 4 5 6 7 8 private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 );private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap <>(16 );private final Map<String, Object> earlySingletonObjects = new HashMap <>(16 );
singletonObjects
用于保存 BeanName
和创建 bean
实例之间的关系,bean-name
–> instanct
singletonFactories
用于保存 BeanName
和创建 bean
的 工厂 之间的关系,bean-name
–> objectFactory
earlySingletonObjects
也是保存 beanName
和创建 bean
实例之间的关系,与 singletonObjects
的不同之处在于 ,当一个单例 bean
被放入到这里之后,那么其他 bean
在创建过程中,就能通过 getBean
方法获取到,目的是用来检测循环引用
后两个Map其实是“垫脚石 ”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。
总结
假设有两个bean CircleA和
CircleB 并循环依赖,他们初始化的过程如下:
创建 beanA
,先记录对应的 beanName
然后将 beanA
的创建工厂 beanFactoryA 放入缓存中
对 beanA
的属性填充方法 populateBean
,检查到依赖 beanB
,缓存中没有 beanB
的实例或者单例缓存,于是要去实例化 beanB
。
开始实例化 beanB
,经历创建 beanA
的过程,到了属性填充方法,检查到依赖了 beanA
。
调用 getBean(A)
方法,在这个函数中,不是真正去实例化 beanA
,而是先去检测缓存中是否有已经创建好的对应的 bean
,或者已经创建好的 beanFactory
检测到 beanFactoryA
已经创建好了,而是直接调用 ObjectFactory
去创建 beanA
结合关键代码梳理流程
创建原始 bean
1 2 3 4 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); // 原始 bean final Object bean = instanceWrapper.getWrappedInstance(); 复制代码
在这一步中,创建的是原始 bean
,因为还没到最后一步属性解析,所以这个类里面没有属性值,可以将它想象成 new ClassA
,同时没有构造函数等赋值的操作,这个原始 bean
信息将会在下一步使用到。
addSingleFactory
1 2 3 // 注释 5.2 解决循环依赖 第二个参数是回调接口,实现的功能是将切面动态织入 bean addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 复制代码
前面也提到过这个方法,它会将需要提前曝光的单例加入到缓存中,将单例的 beanName
和 beanFactory
加入到缓存,在之后需要用到的时候,直接从缓存中取出来。
populateBean 填充属性
刚才第一步时也说过了,一开始创建的只是初始 bean
,没有属性值,所以在这一步会解析类的属性。在属性解析时,会判断属性的类型,如果判断到是 RuntimeBeanReference
类型,将会解析引用。
就像我们写的例子,CircleA
引用了 CircleB
,在加载 CircleA
时,发现 CircleB
依赖,于是乎就要去加载 CircleB
。
我们来看下代码中的具体流程吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { ... if (pvs != null) { // 将属性应用到 bean 中,使用深拷贝,将子类的属性一并拷贝 applyPropertyValues(beanName, mbd, bw, pvs); } } protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { ... String propertyName = pv.getName(); Object originalValue = pv.getValue(); // 注释 5.5 解析参数,如果是引用对象,将会进行提前加载 Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); ... } public Object resolveValueIfNecessary(Object argName, @Nullable Object value) { // 我们必须检查每个值,看看它是否需要一个运行时引用,然后来解析另一个 bean if (value instanceof RuntimeBeanReference) { // 注释 5.6 在这一步中,如果判断是引用类型,需要解析引用,加载另一个 bean RuntimeBeanReference ref = (RuntimeBeanReference) value; return resolveReference(argName, ref); } ... } 复制代码
跟踪到这里,加载引用的流程比较清晰了,发现是引用类的话,最终会委派 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference
进行引用处理,核心的两行代码如下:
1 2 3 4 // 注释 5.7 在这里加载引用的 bean bean = this.beanFactory.getBean(refName); this.beanFactory.registerDependentBean(refName, this.beanName); 复制代码
在这一步进行 CircleB
的加载,但是我们写的例子中,CircleB
依赖了 CircleA
,那它是如何处理的呢,所以这时,我们刚才将 CircleA
放入到缓存中的信息就起到了作用。
getSingleton
还记得之前在类加载时学到的只是么,单例模式每次加载都是取同一个对象,如果在缓存中有,可以直接取出来,在缓存中没有的话才进行加载,所以再来熟悉一下取单例的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); // 检查缓存中是否存在实例 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 记住,公共变量都需要加锁操作,避免多线程并发修改 synchronized (this.singletonObjects) { // 如果此 bean 正在加载则不处理 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 当某些方法需要提前初始化,调用 addSingletonFactory 方法将对应的 // objectFactory 初始化策略存储在 earlySingletonObjects,并且从 singletonFactories 移除 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } 复制代码
虽然 CircleB
引用了 CircleA
,但在之前的方法 addSingletonFactory
时,CircleA
的 beanFactory
就提前暴露。
所以 CircleB
在获取单例 getSingleton()
时,能够拿到 CircleA
的信息,所以 CircleB
顺利加载完成,同时将自己的信息加入到缓存和注册表中,接着返回去继续加载 CircleA
,由于它的依赖已经加载到缓存中,所以 CircleA
也能够顺利完成加载,最终整个加载操作完成~
说白了就是先只是简单构造对象,然后将beanfactory中的bean再进行二次处理,设置对应属性
参考文档