前言spring中的循环依赖和三层地图解决方案,八股文中灾区,坚强的伙伴们可能一字一句背道而驰。 当时的我也是这样,但现实给了我一巴掌。 虽然报告错误晚了
误报:
thedependenciesofsomeofthebeansintheapplicationcontextformacycle
错误2 :
org.spring framework.beans.factory.beancurrentlyincreationexception 3360 errorcreatingbeanwithname‘c’3360 beanwithname initsrawversionaspartofacircularreference、 buthaseventuallybeenwrapped.thismeansthatsaidotherbeansdonotusethefinalversionofthebean.thisisoftentheresultofover-eager annamesoftype’with the‘alloweagerinit’flagturnedoff,for example。
以上都是循环依赖的原因,我很纳闷。 不是说spring已经解决了循环依赖问题吗? 为什么还报错误,好《java面试宝典》 《java架构师指南》诚不欺负我呢!
一、什么是循环依赖?在探索哪些场景会报错之前,让我们先来回顾一下,到底什么是循环依赖
spring场注入,相互引用对方,引起循环依赖的典型场景
@ service class a { @ resourceprivateb; } @ service classb { @ resourceprivatea; }一些伙伴们开始思考了。 这个关系我直接用代码表达很简单呢。 直接给出new,设定值就可以了。 为什么spring方式会出现循环依赖问题?
java代码解决方案如下
A a=new A (; B b=new B (; //依次注入对方A.SETB(B ); b.seta(a ); 通过比较,容易得出以下结论。spring在ioc过程中,生成对象需要流动复杂的bean生命周期,这才在对象交叉引用时产生循环依赖问题。
问题又发生了
本的生命周期是什么呢
bean的生命周期由主线和扩展点组成。
在本文中,您只需要了解主线的流程。 主线为红色,扩展点为蓝色。 按照上述步骤生成每个bean对象。
那么,问题很明显。 注意到创建bean A时需要填充值节点,然后创建bean B,并注意到创建bean B时需要填充值节点,然后创建bean A。 好吧,进入死循环。 这是服从坏的依赖。
二、回顾一下如何解决循环依赖,我们自己编写java代码时,可以很容易地模拟和解决这个对象互相引用的场景呢。 为什么不能进行spring呢? 从根本上说,我们在创建对象时可以获得对其他对象的引用,但是spring无法加入到未创建对象的bean的单实例池中区。 这意味着看不到其他对象的引用。 节点浏览到准备刷新spring缓存的bean节点,最后将其添加到singletonObjects缓存中。
我们知道彼此的存在,但我们是盲人。
因此,spring提供了三层映射机制来解决bean中间状态的可见问题。 核心逻辑如下。
protectedobjectgetsingleton (字符串beanname, boolean allowEarlyReference )//第1层缓存创建的beanobjectsingletonobject=this.singleton object//issingletoncurrentlyrention singleton object==nullissingletoncurrentlyincreation (beanname ) ) singleton object=this.earlysingletonobjects.eats ullallowearlyreference )同步(this.singleton objects (//consistentcreationofearlyrects ) )/consistentcreationof
locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {//第二级缓存,解决中间状态bean对象的可见性问题。singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {//第三级缓存,解决动态代理的幂等性。ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}按访问顺序map排序
singletonObjects 存的是创建好的beanearlySingletonObjects 半成品bean(实例化的空对象,还未填充值),由singletonFactories工厂生成。singletonFactories提前进行aop代理,生成最终的代理类,保证引用不会变化。spring的思路很简单,空对象填充值节点前,通过把半成品bean包装成工厂类放入singletonFactories提前暴露对象引用来保证可见性。
//如果支持循环依赖的话就会进行提前暴露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");}//提前暴露,保证其他bean的可见性addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.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);}}提前暴露的能力靠第二层map就能实现了,为什么还需要搞个工厂来作为第三层map呢?
spring要保证,从map中拿到的引用是最终的引用,即对象的引用不会再改变了。我们想想哪些操作会导致对象引用会改变?大多数情况答案是:spring aop,spring aop的自动化编织,通过扩展点调用BeanPostProcessor的postProcessAfterInitialization()方法,定义BeanPostProcessor来实现,这将会导致对象引用的变化,工厂类的作用就是提前来进行aop操作,来保证获取到最终的对象。
工厂类调用此方法生成第二层map对象
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;}AbstractAutoProxyCreator aop自动编织的实现类,对该方法的重写,提前生成最终对象
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);//编织切面return wrapIfNecessary(bean, beanName, cacheKey);}从源码分析,可能存在不止AbstractAutoProxyCreator一种BeanPostProcessor会改变代理类的引用,spring aop 改变代理类只是大部分的情况,如果存在其他会改变代理类引用的BeanPostProcessor一定要检查是否重写了getEarlyBeanReference方法,不然出现循环依赖的时候将会给你小惊喜。
为什么我不叫它三级缓存呢?
我理解的层级缓存,应该是一级缓存没有,去二级缓存找,二级缓存没有去三级缓存找,类似cpu的L1L2L3缓存,每层缓存都可能会有同一个数据。而spring更像是三层map,因为bean对象实际上只会在其中一个map中。
三、哪些情况下spring循环依赖解决方案会失效?纵观spring循坏依赖解决方案,还是有几个小问题:
无法解决构造函数注入,对应报错一 @Service class A { private B b; @Autowired public A(B b) { this.b = b; }}@Service class B { private A a; @Autowired public B(A a) { this.a = a; }}spring循环依赖解决的就是就是中间状态对象的可见性问题,earlySingletonObjects层会缓存中间状态对象引用,如果beanA需要在构造函数结束前去创建beanB,这样无法产生中间状态的beanA,无法满足可见性问题,从而报错一;
解决方式如下
@Service class A { private B b; @Autowired public A(@Lazy B b) { this.b = b; }}通过添加@Lazy注解解决,具体在创建beanA时候引入beanB,不会真正去创建beanB,而是得到一个由@Lazy生成的代理类,beanA拿到该代理类创建完毕放入singletonObjects,beanB在真正使用的时候开始创建,这个时候beanA已经在singletonObjects中了,自然不会发生循坏依赖问题·。
如果存在不止一种BeanPostProcessor改变代理类的引用,没有重写getEarlyBeanReference方法,对应报错二 @Service@Async@EnableAsyncclass C { @Resource private D d ;}@Servicepublic class D { @Resource private C c;}@Async会通过代理,通过BeanPostProcessor扩展点增强类的功能,提供类方法异步的能力,新生成的代理类会改变原始类对象的引用。我们来看看具体的实现类AsyncAnnotationBeanPostProcessor。
好家伙,完全没有重写getEarlyBeanReference,结果报错二,出处如下。
在doCreateBean核心方法处以前看不懂的代码也可以解释了,代理对象的引用发生变化,导致spring认为有两个不同版本的bean加入到了容器内,对于这种情况spring也无能为力,只得抛出一个错,告诉程序员,有人在害你。
四、总结 spring的三层map解决的根本问题就是中间状态对象的可见性。spring的三层map解决方案,仅适用于字段注入下的循环依赖场景BeanPostProcessor改变对象引用的情况下,需要查看是否会发生循环引用,如果是,需要查看是否重写了getEarlyBeanReference方法,否则会报错。