这篇文章主要介绍Spring循环依赖的解决方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
循环依赖其实就是循环引用,很多地方都说需要两个或则两个以上的bean互相持有对方最终形成闭环才是循环依赖,比如A依赖于B,B依赖于C,C又依赖于A。其实一个bean持有自己类型的属性也会产生循环依赖。
setter singleton循环依赖
使用
SingleSetterBeanA依赖SingleSetterBeanB,SingleSetterBeanB依赖SingleSetterBeanA。
@Datapublic class SingleSetterBeanA {@Autowiredprivate SingleSetterBeanB singleSetterBeanB;}
@Datapublic class SingleSetterBeanB {@Autowiredprivate SingleSetterBeanA singleSetterBeanA;}
源码分析
spring是通过三级缓存来解决循环依赖的,那么三级缓存是怎么工作的呢?
三级缓存对应org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类的三个属性:
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 ConcurrentHashMap<>(16); // 三级缓存
对于setter注入造成的依赖是通过Spring容器提前暴露刚完成实例化但未完成初始化的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean,关键源码如下所示:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// 处理循环依赖,实例化后放入三级缓存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));}
bean实例化后放入三级缓存中:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
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);}}}
放入三级缓存中的是ObjectFactory类型的lambda表达式:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#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;// 使用AbstractAutoProxyCreator#getEarlyBeanReference创建代理对象exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}
构造器参数循环依赖
通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。
使用
@Datapublic class SingleConstrutorBeanA {public SingleConstrutorBeanA(SingleConstrutorBeanB singleConstrutorBeanB) {}}
@Datapublic class SingleConstrutorBeanB {public SingleConstrutorBeanB(SingleConstrutorBeanA singleConstrutorBeanA) {}}
上面的代码运行时会抛出如下异常:
... ...Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'singleConstrutorBeanB': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleConstrutorBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:805)at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:228)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1403)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1245)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:538)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:329)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240)at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:892)at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:796)... 76 moreCaused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleConstrutorBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240)at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:892)at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:796)... 90 more
源码分析
Spring容器会将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}}
@Lazy打破循环依赖
在上面的例子中只需要在SingleConstrutorBeanA或者SingleConstrutorBeanB的构造方法上面加上@Lazy注解,就会发现不会抛出异常了,这又是为什么呢?
下面假设在SingleConstrutorBeanA的构造方法上面加了@Lazy注解,在构造B时,发现参数A时被@Lazy注解修饰时,那么就不会调用getBean来获取对象,而是创建了一个代理对象,所以不会构成真正的循环依赖,不会抛出BeanCurrentlyInCreationException异常。
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// 调用beanFactory.getBean(beanName)从容器中获取依赖对象result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;
setter prototype循环依赖
对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存"prototype"作用域的bean,因此无法提前暴露一个创建中的bean。
使用
@Data@Component@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class PrototypeBeanA {@Autowiredprivate PrototypeBeanB prototypeBeanB;}
@Data@Component@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class PrototypeBeanB {@Autowiredprivate PrototypeBeanA prototypeBeanA;}
@Testpublic void test3() {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();applicationContext.register(PrototypeBeanA.class);applicationContext.register(PrototypeBeanB.class);applicationContext.refresh();applicationContext.getBean(PrototypeBeanA.class); // 此时必须要获取Spring管理的实例,因为现在scope="prototype" 只有请求获取的时候才会实例化对象}
运行结果如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:269)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1322)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:668)... 89 more
源码分析
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
... ...// 判断是否在当前创建Bean池中if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}... ...
异常就是在上面的代码中抛出来的,那么beanName是什么时候添加至当前创建Bean池中的呢?
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.// prototype类型的bean的实例化Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}
org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation
protected void beforePrototypeCreation(String beanName) {// ThreadLocalObject curVal = this.prototypesCurrentlyInCreation.get();if (curVal == null) {this.prototypesCurrentlyInCreation.set(beanName);}else if (curVal instanceof String) {Set<String> beanNameSet = new HashSet<>(2);beanNameSet.add((String) curVal);beanNameSet.add(beanName);this.prototypesCurrentlyInCreation.set(beanNameSet);}else {Set<String> beanNameSet = (Set<String>) curVal;beanNameSet.add(beanName);}}
其根本原因就是Spring容器不会对prototype类型的bean进行缓存,因此无法提前利用三级缓存暴露一个代理对象。
循环依赖开关
可以通过allowCircularReferences来禁止循环依赖,这样的话,singleton bean的setter循环依赖也会报错。
二级缓存可行?
缓存 | 说明 |
---|---|
singletonObjects | 第一级缓存,存放可用的成品Bean。 |
earlySingletonObjects | 第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化,用以解决循环依赖。 |
singletonFactories | 第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中,用以解决循环依赖。 |
理论上二级缓存时可行的,只需要将三级缓存中BeanFactory创建的对象提前放入二级缓存中,这样三级缓存就可以移除了。
那么spring中为什么还要使用三级缓存呢?如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
以上是“Spring循环依赖的解决方法”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网行业资讯频道!