首页 > 编程知识 正文

spring启动过程,spring循环注入怎么解决

时间:2023-05-04 06:46:53 阅读:163022 作者:2273

前言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,结果报错二,出处如下。

//earlySingletonReference 循环引用生成的代理类//exposedObject 本方法生成的代理类//bean 原始引用if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);//如果走到这里,说明触发了循环依赖if (earlySingletonReference != null) {//如果相等,这里是指发生了aop根据cacheKey判定到幂等if (exposedObject == bean) {exposedObject = earlySingletonReference;//走到这里说明有其他钩子(后置处理器)把bean替换了,所以要检查在此之前是否已经发生过该bean的依赖注入,如果发生,就导致一个bean的不同版本被注入,针对这种情况,会抛出异常} 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 " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");}}}}

在doCreateBean核心方法处以前看不懂的代码也可以解释了,代理对象的引用发生变化,导致spring认为有两个不同版本的bean加入到了容器内,对于这种情况spring也无能为力,只得抛出一个错,告诉程序员,有人在害你。

四、总结 spring的三层map解决的根本问题就是中间状态对象的可见性。spring的三层map解决方案,仅适用于字段注入下的循环依赖场景BeanPostProcessor改变对象引用的情况下,需要查看是否会发生循环引用,如果是,需要查看是否重写了getEarlyBeanReference方法,否则会报错。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。