前言 循环依赖:就是N个类循环(嵌套)引用 。通俗的讲就是N个Bean互相引用对方,最终形成闭环
。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):
注意:其实可以N=1,也就是极限情况的循环依赖:自己依赖自己
另需注意:这里指的循环引用不是方法之间的循环调用,而是对象的相互依赖关系 。(方法之间循环调用若有出口也是能够正常work的)
可以设想一下这个场景:如果在日常开发中我们用new对象的方式,若构造函数之间发生这种循环依赖 的话,程序会在运行时一直循环调用最终导致内存溢出 ,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main { public static void main (String[] args) throws Exception { System.out.println(new A()); } } class A { public A () { new B(); } } class B { public B () { new A(); } }
运行报错:
1 Exception in thread "main" java.lang.StackOverflowError
这是一个典型的循环依赖问题。本文说一下Spring
是如果巧妙的解决平时我们会遇到的三大循环依赖问题
的。
Spring Bean的循环依赖 谈到Spring Bean
的循环依赖,有的小伙伴可能比较陌生,毕竟开发过程中好像对循环依赖
这个概念无感知 。其实不然,你有这种错觉,权是因为你工作在Spring的襁褓
中,从而让你“高枕无忧”。我十分坚信,小伙伴们在平时业务开发中一定一定写过如下结构的代码:
1 2 3 4 5 6 7 8 9 10 11 12 @Service public class AServiceImpl implements AService { @Autowired private BService bService; ... } @Service public class BServiceImpl implements BService { @Autowired private AService aService; ... }
这其实就是Spring环境下典型的循环依赖场景。但是很显然,这种循环依赖场景,Spring已经完美的帮我们解决和规避了问题。所以即使平时我们这样循环引用,也能够整成进行我们的coding之旅。
Spring中三大循环依赖场景
演示 在Spring环境中,因为我们的Bean的实例化、初始化都是交给了容器,因此它的循环依赖主要表现为下面三种场景。为了方便演示,我准备了如下两个类:
1、构造器注入循环依赖 1 2 3 4 5 6 7 8 9 10 @Service public class A { public A (B b) { } } @Service public class B { public B (A a) { } }
结果:项目启动失败抛出异常BeanCurrentlyInCreationException
1 2 3 4 5 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? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
构造器注入构成的循环依赖,此种循环依赖方式是无法解决的 ,只能抛出BeanCurrentlyInCreationException
异常表示循环依赖。这也是构造器注入的最大劣势(它有很多独特的优势,请小伙伴自行发掘)根本原因
:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化
,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决。
2、field属性注入(setter方法注入)循环依赖 这种方式是我们最最最最 为常用的依赖注入方式(所以猜都能猜到它肯定不会有问题啦):
1 2 3 4 5 6 7 8 9 10 11 @Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; }
结果:项目启动成功,能够正常work
备注:setter方法注入方式因为原理和字段注入方式类似,此处不多加演示
3、prototype
field属性注入循环依赖 prototype
在平时使用情况较少,但是也并不是不会使用到,因此此种方式也需要引起重视。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Service public class A { @Autowired private B b; } @Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Service public class B { @Autowired private A a; }
结果:需要注意的是 本例中启动时是不会报错的 (因为非单例Bean默认
不会初始化,而是使用时才会初始化),所以很简单咱们只需要手动getBean()
或者在一个单例Bean内@Autowired
一下它即可
1 2 3 @Autowired private A a;
这样子启动就报错:
1 2 3 4 5 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean' : Unsatisfied dependency expressed through field 'a' ; nested exception is 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? at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement .inject(AutowiredAnnotationBeanPostProcessor.java:596) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
如何解决???可能有的小伙伴看到网上有说使用@Lazy
注解解决:
1 2 3 @Lazy @Autowired private A a;
此处负责任的告诉你这样是解决不了问题的(可能会掩盖问题 ),@Lazy
只是延迟初始化而已,当你真正使用到它(初始化)的时候,依旧会报如上异常。 对于Spring循环依赖的情况总结如下:
不能解决的情况:构造器注入循环依赖 prototype
field属性注入循环依赖 能解决的情况:field属性注入(setter方法注入)循环依赖 Spring解决循环依赖的原理分析 在这之前需要明白java中所谓的引用传递
和值传递
的区别。
说明:看到这句话可能有小伙伴就想喷我了。java中明明都是传递啊,这是我初学java时背了100遍的面试题,怎么可能有错??? 这就是我做这个申明的必要性:伙计,你的说法是正确的,java中只有值传递
。但是本文借用引用传递
来辅助讲解,希望小伙伴明白我想表达的意思。
Spring的循环依赖的理论依据基于Java的引用传递
**,当获得对象的引用时, 对象的属性是可以延后设置的**。(但是构造器必须是在获取引用之前,毕竟你的引用是靠构造器给你生成的,儿子能先于爹出生?哈哈)
Spring创建Bean的流程 首先需要了解是Spring它创建Bean的流程,我把它的大致调用栈绘图如下: 对Bean的创建最为核心三个方法解释如下:
createBeanInstance
:例化,其实也就是调用对象的构造方法 实例化对象populateBean
:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired
)initializeBean
:回到一些形如initMethod
、InitializingBean
等方法从对单例Bean
的初始化可以看出,循环依赖主要发生在第二步(populateBean) ,也就是field属性注入的处理。
Spring容器的'三级缓存'
在Spring容器的整个声明周期中,单例Bean有且仅有一个对象。这很容易让人想到可以用缓存来加速访问。 从源码中也可以看出Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至不惜使用了“三级缓存”,这也便是它设计的精妙之处。三级缓存
其实它更像是Spring容器工厂的内的术语
,采用三级缓存模式来解决循环依赖问题,这三级缓存分别指:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256 ); private final Map<String, Object> earlySingletonObjects = new HashMap<>(16 ); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16 ); ... private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16 )); private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256 )); }
注:AbstractBeanFactory
继承自DefaultSingletonBeanRegistry
。
singletonObjects
:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 earlySingletonObjects
:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖singletonFactories
:单例对象工厂的cache,存放 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 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... @Override @Nullable public Object getSingleton (String beanName) { return getSingleton(beanName, true ); } @Nullable protected Object getSingleton (String beanName, boolean allowEarlyReference) { Object singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this .singletonObjects) { singletonObject = this .earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this .singletonFactories.get(beanName); if (singletonFactory != null ) { singletonObject = singletonFactory.getObject(); this .earlySingletonObjects.put(beanName, singletonObject); this .singletonFactories.remove(beanName); } } } } return singletonObject; } ... public boolean isSingletonCurrentlyInCreation (String beanName) { return this .singletonsCurrentlyInCreation.contains(beanName); } protected boolean isActuallyInCreation (String beanName) { return isSingletonCurrentlyInCreation(beanName); } ... }
先从一级缓存singletonObjects
中去获取。(如果获取到就直接return) 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()
),那就再从二级缓存earlySingletonObjects
中获取。(如果获取到就直接return) 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()
获取。就从三级缓存singletonFactory
.getObject()获取。(如果获取到了就从 **singletonFactories**
中移除,并且放进 **earlySingletonObjects**
。其实也就是从三级缓存 **移动(是剪切、不是复制哦!)**
到了二级缓存) 加入singletonFactories
三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
getSingleton()
从缓存里获取单例对象步骤分析可知,Spring解决循环依赖的诀窍:就在于singletonFactories这个三级缓存 。这个Cache里面都是ObjectFactory
,它是解决问题的关键。
1 2 3 4 5 @FunctionalInterface public interface ObjectFactory <T > { T getObject () throws BeansException ; }
经过ObjectFactory.getObject()后,此时放进了二级缓存earlySingletonObjects
内。这个时候对象已经实例化了,虽然还不完美
,但是对象的引用已经可以被其它引用了。
此处说一下二级缓存earlySingletonObjects
它里面的数据什么时候添加什么移除??? 添加 :向里面添加数据只有一个地方,就是上面说的getSingleton()
里从三级缓存里挪过来移除 :addSingleton、addSingletonFactory、removeSingleton
从语义中可以看出添加单例、添加单例工厂ObjectFactory
的时候都会删除二级缓存里面对应的缓存值,是互斥的
源码解析 Spring
容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,而对于创建完毕的Bean将从当前创建Bean池
中清除掉。 这个“当前创建Bean池”指的是上面提到的singletonsCurrentlyInCreation
那个集合。
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { ... protected <T> T doGetBean (final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { ... Object sharedInstance = getSingleton(beanName); ... if (!typeCheckOnly) { markBeanAsCreated(beanName); } ... if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } ... } public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { ... protected Object doCreateBean (final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { ... instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); ... 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; ... populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); 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." ); } } } } return exposedObject; } protected boolean removeSingletonIfCreatedForTypeCheckOnly (String beanName) { if (!this .alreadyCreated.contains(beanName)) { removeSingleton(beanName); return true ; } else { return false ; } } }
这里举例:例如是field
属性依赖注入,在populateBean
时它就会先去完成它所依赖注入的那个bean的实例化、初始化过程,最终返回到本流程继续处理,因此Spring这样处理是不存在任何问题的。 这里有个小细节:
1 2 3 if (exposedObject == bean) { exposedObject = earlySingletonReference; }
这一句如果exposedObject == bean
表示最终返回的对象就是原始对象,说明在populateBean
和initializeBean
没对他代理过,那就啥话都不说了exposedObject = earlySingletonReference
,最终把二级缓存里的引用返回即可。
流程总结(非常重要
) 此处以如上的A、B类的互相依赖注入为例,在这里表达出关键代码 的走势: 1、入口处即是实例化、初始化A这个单例Bean 。AbstractBeanFactory.doGetBean("a")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected <T> T doGetBean (...) { ... if (!typeCheckOnly) { markBeanAsCreated(beanName); } Object beanInstance = getSingleton(beanName, false ); ... sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); }); }
2、下面进入到最为复杂的AbstractAutowireCapableBeanFactory.createBean/doCreateBean()
环节,创建A的实例
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 protected Object doCreateBean () { ... instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); ... boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } ... Object exposedObject = bean; populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); ... 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." ); } } } } }
由于关键代码部分的步骤不太好拆分,为了更具象表达,那么使用下面一副图示帮助小伙伴们理解:
最后的最后,由于我太暖心了_,再来个纯文字版的总结。 依旧以上面A
、B
类使用属性field
注入循环依赖的例子为例,对整个流程做文字步骤总结如下:
使用context.getBean(A.class)
,旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路 实例化
A(注意此处仅仅是实例化),并将它放进缓存
(此时A已经实例化完成,已经可以被引用了)初始化
A:@Autowired
依赖注入B(此时需要去容器内获取B)为了完成依赖注入B,会通过getBean(B)
去容器内找B。但此时B在容器内不存在,就走向B的创建之路 实例化
B,并将其放入缓存。(此时B也能够被引用了)初始化
B,@Autowired
依赖注入A(此时需要去容器内获取A)此处重要
:初始化B时会调用getBean(A)
去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)
能够正常返回B初始化成功 (此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)
这句代码,回到了初始化A的流程中)。因为B实例已经成功返回了,因此最终A也初始化成功 到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B,完美 站的角度高一点,宏观上看Spring处理循环依赖的整个流程就是如此。希望这个宏观层面的总结能更加有助于小伙伴们对Spring解决循环依赖的原理的了解,同时也顺便能解释为何构造器循环依赖就不好使的原因。
循环依赖对AOP代理对象创建流程和结果
的影响 我们都知道Spring AOP、事务 等都是通过代理对象来实现的,而事务 的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象 。 本文结合循环依赖
,回头再看AOP代理对象的创建过程,和最终放进容器内的动作,非常有意思。
1 2 3 4 5 6 7 8 9 10 11 @Service public class HelloServiceImpl implements HelloService { @Autowired private HelloService helloService; @Transactional @Override public Object hello (Integer id) { return "service hello" ; } }
此Service
类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy
。刚好它又存在自己引用自己
的循环依赖。看看这个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 protected Object doCreateBean ( ... ) { ... boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); ... if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != null ) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } } ... } }
上演示的是代理对象+自己存在循环依赖
的case:Spring用三级缓存很巧妙的进行解决了。若是这种case:代理对象,但是自己并不存在循环依赖 ,过程稍微有点不一样儿了,如下描述:
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 protected Object doCreateBean ( ... ) { ... addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); ... populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); ... if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != null ) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } }... ... ... } }
分析可知,即使自己只需要代理,并不被循环引用,最终存在Spring容器里的仍旧是 代理对象。(so此时别人直接@Autowired
进去的也是代理对象呀)终极case:如果我关闭Spring容器的循环依赖能力,也就是把allowCircularReferences
设值为false,那么会不会造成什么问题呢?
1 2 3 4 5 6 7 8 @Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) throws BeansException { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false ); } }
若关闭了循环依赖后,还存在上面A、B的循环依赖现象,启动便会报错如下:
1 2 3 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? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
注意此处异常类型也是BeanCurrentlyInCreationException
异常,但是文案内容和上面强调的有所区别,它报错位置在:DefaultSingletonBeanRegistry.beforeSingletonCreation
这个位置。
报错浅析
:在实例化A后给其属性赋值时,会去实例化B。B实例化完成后会继续给B属性赋值,这时由于此时我们关闭了循环依赖
,所以不存在提前暴露
引用这么一说来给实用。因此B无法直接拿到A的引用地址,因此只能又去创建A的实例。而此时我们知道A其实已经正在创建中了 ,不能再创建了。so,就报错了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Service public class HelloServiceImpl implements HelloService { @Transactional @Override public Object hello (Integer id) { return "service hello" ; } }
这样它的大致运行如下:
1 2 3 4 5 6 7 8 9 10 11 12 protected Object doCreateBean ( ... ) { boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); ... populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); if (earlySingletonExposure) { ... 这里更不用说,因为earlySingletonExposure=false 所以上面的代理对象exposedObject 直接return 了。 } }
可以看到即使把这个开关给关了,最终放进容器了的仍旧是代理对象,显然@Autowired
给属性赋值的也一定是代理对象。 最后,以AbstractAutoProxyCreator
为例看看自动代理创建器是怎么配合实现:循环依赖+创建代理
AbstractAutoProxyCreator
是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator
、AspectJAwareAdvisorAutoProxyCreator
、AnnotationAwareAspectJAutoProxyCreator
小伙伴们应该会更加的熟悉些
该抽象类实现了创建代理的动作:
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 public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor , BeanFactoryAware { ... @Override public Object getEarlyBeanReference (Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this .earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } @Override public Object postProcessAfterInitialization (@Nullable Object bean, String beanName) { if (bean != null ) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this .earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } ... }
由上可知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。 **上面分析了三种case,现给出结论如下:
** 不管是自己被循环依赖了还是没有,甚至是把Spring容器的循环依赖给关了 ,它对AOP代理的创建流程有影响,但对结果是无影响的。 也就是说Spring很好的对调用者屏蔽了这些实现细节,使得使用者使用起来完全的无感知。
总结 解决此类问题的关键是要对SpringIOC
和DI
的整个流程做到心中有数,要理解好本文章,建议有【相关阅读】里文章的大量知识的铺垫,同时呢本文又能进一步 的帮助小伙伴理解到Spring Bean的实例化、初始化流程。 本文还是花了我一番心思的,个人觉得对Spring这部分的处理流程描述得还是比较详细的,希望我的总结能够给大家带来帮助。另外为了避免循环依赖导致启动问题而又不会解决,有如下建议:
业务代码中
尽量不要使用构造器注入,即使它有很多优点。业务代码中
为了简洁,尽量使用field注入而非setter方法注入若你注入的同时,立马需要处理一些逻辑(一般见于框架设计中,业务代码中不太可能出现),可以使用setter方法注入辅助完成 作者: YourBatman 原文: https://cloud.tencent.com/developer/article/1497692