当前位置: 首页 > 科技观察

Springboot源码分析Spring循环依赖揭秘

时间:2023-03-22 17:30:10 科技观察

摘要:如果你是一个有经验的程序员,你在开发中一定遇到过这种现象:事务不生效。可能只是说到这里,有些朋友就会感到震惊。Spring不是解决了循环依赖的问题吗?怎么又发生了?,然后让我们揭开Spring循环依赖的最本质原因。Spring循环依赖流程图Spring循环依赖的原因是使用具有代理特性的BeanPostProcessor。典型的有事务注解@Transactional,异步注解@Async等。源码分析揭示protectedObjectdoCreateBean(...){...booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&&isSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure){addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));}...//populateBean是一个特殊的key,需要给A属性赋值,所以这里我们实例化B~~//而B我们从上面可以看出它是一个普通的Bean(不需要创建代理对象),实例化完成后,继续给他的属性A赋值,这时候就会去获取A的earlyreference//就是在这里给B的属性a赋值时,会执行上面放的BeanA进程中的getEarlyBeanReference()方法来获取A的Earlyreference~~//当执行A的getEarlyBeanReference()方法,自动代理creator会被执行,但是因为A没有标记事务,所以最终不会创建代理,所以B的合格属性引用会是A的**原始对象**//需要注意的是@Async的代理对象不是在getEarlyBeanReference()中创建的,是在postProcessAfterInitialization中创建的proxy//从这里我们也可以看出@Async的proxy默认是不支持你循环引用的,因为它没有提供proxy的earlyreferenceobject~~~(注意这个和自动代理创建器的区别~)//结论:这里给A的依赖属性字段B赋值了一个B的实例(因为B不需要创建代理,所以它是原始对象)//这里实例B中依赖A的注入仍然是BeanA的普通实例对象(注意原始对象不是代理对象)注意:此时暴露的对象还是原始对象对象填充Bean(beanName,mbd,instanceWrapper);//这里会生成@Async标记的Bean的代理对象~~~参考类:AsyncAnnotationBeanPostProcessor//所以这句话执行完后,exposedObject会是一个代理对象,而不是原来的objectexposedObject=initializeBean(beanName,exposedObject,mbd);...//这里是报错的重点~~~if(earlySingletonExposure){//说是A循环依赖B,所以放Ain此时有二级缓存,所以这里的earlySingletonReference是对A的原始对象的引用earlySingletonReference=null直接返回)ObjectearlySingletonReference=getSingleton(beanName,false);if(earlySingletonReference!=null){//上面分析过,exposedObject是被@Aysnc代理过的对象,bean是原始对象,所以这里不等于走else逻辑if(exposedObject==bean){exposedObject=earlySingletonReference;}//allowRawInjectionDespiteWrapping标记是否全部ow这个bean的原始类型要注入到其他bean中,即使你最终会被wrapped(proxy)//默认是false不允许,如果改成true允许,则不会报错这是我们后面要讲的解决方案之一~~~//另外dependentBeanMap记录了每个Bean所依赖的Bean的Map~~~~elseif(!this.allowRawInjectionDespiteWrapping&&hasDependentBean(beanName)){//我们的BeanA依赖B,所以这里的值为["b"]String[]dependentBeans=getDependentBeans(beanName);SetactualDependentBeans=newLinkedHashSet<>(dependentBeans.length);//一一检查所有依赖~例如,B这里会有问题//"b"在removeSingletonIfCreatedForTypeCheckOnly后会返回false,因为它已经存在于alreadyCreated中,也就是说B已经完全创建了~~~//而b完成了,所以属性a也是的赋值完成,但是B中引用的a不等于主进程中我的A,所以一定有问题(说明不是final的)~~~//所以最终会添加到actualDependentBeans中,说明A是真正的Depends~~~for(StringdependentBean:dependentBeans){if(!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)){actualDependentBeans.add(dependentBean);}}//如果有这样一个真正的依赖,就会报错~~~那么异常就是上面看到的异常信息if(!actualDependentBeans.isEmpty()){thrownewBeanCurrentlyInCreationException(beanName,"Beanwithname'"+beanName+"'hasbeeninjectedintootherbeans["+StringUtils.collectionToCommaDelimitedString(actualDependentBeans)+"]初始化原始版本作为循环引用的一部分,但最终被"+"包装。这意味着其他beans不使用"+"bean的最终版本。这通常会导致过度急切的类型匹配-考虑使用"+"'getBeanNamesOfType'withthe'allowEagerInit'flagturned}off.}"}...循环依赖对象earlySingletonReference=getSingleton(beanName,false);必须有一个值缓存工厂addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));会给实例对象添加SmartInstantiationAwareBeanPostProcessorAbstractAutoProxyCreator是SmartInstantiationAwareBeanPostProcessor的子类,一定要记住,一定要记住,SmartInstantiationAwareBeanPostProcessor的子类很重要!!!!!exposedObject=initializeBean(beanName,exposedObject,mbd);执行BeanPostProcessor后处理,关注BeanPostProcessor!!!!!Spring的循环依赖通过它的三级缓存很容易解决,但是这两个地方的后处理带来了循环依赖的问题对比AbstractAdvisorAutoProxyCreator和AsyncAnnotationBeanPostProcessor,因为SmartInstantiationAwareBeanPostProcessor的子类会在两处进行后处理,所以前后都会有相同的对象引用,不会出现循环依赖问题。异步注释将不起作用。为什么?自己看上面的分析,仔细看!如何解决循环依赖?改变加载顺序@Lazy注解设置allowRawInjectionDespiteWrapping为true(使用判断语句)不要使用相关BeanPostProcessor设计的注解,哈哈,这不太现实。@Lazy@Lazy一般表示延迟加载,它只会作用于BeanDefinition.setLazyInit()。并且这里给它增加了一个能力:延迟处理(proxyprocessing)//@since4.0出现的比较晚,它支持@Lazy是功能最全的AutowireCandidateResolverpublicclassContextAnnotationAutowireCandidateResolverextendsQualifierAnnotationAutowireCandidateResolver{//这个类自己做的只有这个,这里细分析//返回lazyproxy意味着延迟初始化。实现过程是检查@Autowired注解处是否使用了@Lazy=true注解@Override@NullablepublicObjectgetLazyResolutionProxyIfNecessary(DependencyDescriptordescriptor,@NullableStringbeanName){//如果isLazy=true则返回一个代理,否则返回null//相当于if@Lazy注解被标记,会返回一个代理(当然@Lazy注解的值不能为false)return(isLazy(descriptor)?buildLazyResolutionProxy(descriptor,beanName):null);}//这个比较简单,只要标注@Lazy注解即可(value属性默认值为true)//@Lazy支持属性和方法入参的注解~~~这里会解析protectedbooleanisLazy(DependencyDescriptordescriptor){for(Annotationann:descriptor.getAnnotations()){Lazylazy=AnnotationUtils.getAnnotation(ann,Lazy.class);if(lazy!=null&&lazy.value()){returntrue;}}MethodParametermethodParam=descriptor.getMethodParameter();if(methodParam!=null){Methodmethod=methodParam.getMethod();if(method==null||void.class==method.getReturnType()){Lazylazy=AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(),Lazy.class);if(lazy!=null&&lazy.value()){returntrue;}}}returnfalse;}//核心内容是这个类的灵魂~~~protectedObjectbuildLazyResolutionProxy(finalDependencyDescriptordescriptor,final@NullableStringbeanName){Assert.state(getBeanFactory()instanceofDefaultListableBeanFactory,"BeanFactoryneedstobeaDefaultListableBeanFactory");DefaultListableBeanFactory.doResolveDependency()方法~~~finalDefaultListableBeanFactorybeanFactory=(DefaultListableBeanFactory)getBeanFactory();//TargetSource是其懒加载的核心原因。这个接口在AOP章节提到过,这里不再赘述//它有很多比较知名的实现,比如HotSwappableTargetSource,SingletonTargetSource,LazyInitTargetSource,//SimpleBeanTargetSource,ThreadLocalTargetSource,PrototypeTargetSource等//这里有很多,因为你只需要自己用,它是通过匿名内部类实现的~~~这里最重要的看getTarget方法,在使用的时候执行(也就是真正使用代理对象的时候~~~)TargetSources=newTargetSource(){@OverridepublicClassgetTargetClass(){重新turndescriptor.getDependencyType();}@OverridepublicbooleanisStatic(){returnfalse;}//调用代理方法时会调用getTarget,所以每个代理方法都会执行这个方法,这就是为什么doResolveDependency//我个人认为是在效率方面,是有一定问题的~~~所以这里建议尽量少用@Lazy~~~//不过效率应该不错,和http相比,序列化和反序列化处理,简直不值提一下,所以还是没关系@OverridepublicObjectgetTarget(){Objecttarget=beanFactory.doResolveDependency(descriptor,beanName,null,null);if(target==null){Classtype=getTargetClass();//null值用于多值注入友好处理(不要使用null)}elseif(Set.class==type||Collection.class==type){returnCollections.emptySet();}thrownewNoSuchBeanDefinitionException(描述符.getResolvableType(),"Optionaldependencynotpresentforlazyinjectionpoint");}returntarget;}@OverridepublicvoidreleaseTarget(Objecttarget){}};//使用ProxyFactory为ts生成一个代理//可以看出最终生成的代理对象的目标对象为其实就是TargetSource,而TargetSource的目标就是我们业务的对象ProxyFactorypf=newProxyFactory();pf.setTargetSource(ts);ClassdependencyType=descriptor.getDependencyType();//如果注入语句这样写privateAInterfacea;thenthistypeistheexcusevalueistrue//这个接口类型也必须放进去(不然这个proxy不属于这个类型,反射集不是直接报错了吗????)if(dependencyType.isInterface()){pf.addInterface(dependencyType);}returnpf.getProxy(beanFactory.getBeanClassLoader());}}打上@Lazy注解完成注入的时候,最后的注入只是在这里临时生成一个代理对象。只有真正执行到目标方法时,才会在容器中获取真正的bean实例来执行目标方法。使用allowRawInjectionDespiteWrapping属性强制改变判断@ComponentpublicclassMyBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{((AbstractAutowireCapableBeanFactory)beanFactory).setAllowRawInjectionDespiteWrapping{((AbstractAutowireCapableBeanFactory)beanFactory).setAllowRawInjectionDespiteWrapping}这将导致容器中的原始对象暴露给引用,并暴露给代理对象(true);其他情况,不生效。由于它只影响循环依赖中的bean,影响范围不是全局的,所以当找不到更好的方法时,这是一个很好的解决方案。

最新推荐
猜你喜欢