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

闺蜜们可以理解了,Spring是怎么解决循环依赖的呢?

时间:2023-03-21 22:22:01 科技观察

本文转载自微信公众号“Java知识堂”,作者李利民。转载本文请联系爪哇石塘公众号。介绍先说一下什么是循环依赖。Spring初始化A的时候需要注入B,初始化B的时候需要注入A,Spring启动后,这两个bean都要初始化。Spring的循环依赖有两种场景结构构造函数的循环依赖属性循环依赖和构造函数的循环依赖可以通过在构造函数中使用@Lazy注解实现延迟加载。注入依赖时,先注入代理对象,在第一次使用时再创建对象,完成注入属性的循环依赖。构造函数的循环依赖主要通过3个map来解决constructorB=constructorB;}}@ComponentpublicclassConstructorB{privateConstructorAconstructorA;@AutowiredpublicConstructorB(ConstructorAconstructorA){this.constructorA=constructorA;}}@Configuration@ComponentScan("com.javashitang.dependency.constructor")publicclassConstructorConfig{}publicclassConstructorMain{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(ConstructorConfig.class);System.out.println(context.getBean(ConstructorA.class));System.out.println(context.getBean(ConstructorB.class));}}运行主要的ConstructorMain方法,第一行会报异常,说明Spring无法初始化所有的bean,即上述形式的循环依赖是Spring无法解决的。我们可以在ConstructorA或者ConstructorB构造函数的参数上加上@Lazy注解来解决@AutowiredpublicConstructorB(@LazyConstructorAconstructorA){this.constructorA=constructorA;}因为我们主要关注属性的循环依赖,构造函数的循环依赖不做属性循环依赖的过多分析首先演示一下什么是属性循环依赖{}publicclassFieldMain{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(FieldConfig.class);//com.javashitang.dependency.field.FieldA@3aa9e816System.out.println(context.getBean(FieldA.class));//com。javashitang.dependency.field.FieldB@17d99928System.out.println(context.getBean(FieldB.class));}}Spring容器正常启动,可以得到两个Bean属性FieldA和FieldB的循环依赖,即面试时常被问到。总体来说并不复杂,但是涉及到SpringBean的初始化过程,所以感觉比较复杂。我会写一个demo来演示整个过程。SpringBean的初始化过程其实比较复杂。为了便于理解Demo,我将使用SpringBean的初始化过程分为两部分:bean的实例化过程,即bean通过调用构造函数创建对象的初始化过程,即,bean的各种属性的填充,bean的初始化过程就完成了,可以正常创建bean了,ObjectFactory接口用于生产bean,与Spring中定义的接口相同publicinterfaceObjectFactory{TgetObject();}publicclassDependencyDemo{//InitializedBeanprivatefinalMapsingletonObjects=newConcurrentHashMap<>(256);//正在被初始化的Bean对应的工厂,此时对象已经被实例化了privatefinalMap>singletonFactories=newHashMap<>(16);//存放被初始化的Bean,放入在实例化对象之前privatefinalSetsingletonsCurrentlyInCreation=Collections.newSetFromMap(newConcurrentHashMap<>(16));publicTgetBean(ClassbeanClass)throwsException{//类名是Bean的名字StringbeanName=beanClass.getSimpleName();//已经初始化,或者正在初始化ObjectinitObj=getSingleton(beanName,true);if(initObj!=null){return(T)initObj;}//bean正在初始化singletonsCurrentlyInCreation.add(beanName);//实例化beanObjectobject=beanClass.getDeclaredConstructor().newInstance();singletonFactories.put(beanName,()->{returnobject;});//开始初始化bean,即填充属性Field[]fields=object.getClass().getDeclaredFields();for(Fieldfield:fields){field.setAccessible(true);//获取classClassfieldClass=field.getType();field.set(object,getBean(fieldClass));}//初始化完成singletonObjects.put(beanName,object);singletonsCurrentlyInCreation.remove(beanName);return(T)object;}/***allowEarlyReference参数表示Spring是否允许循环依赖,默认为true*所以当allowEarlyReference设置为false时,当项目有循环依赖时,会启动失败*/publicObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ObjectsingletonObject=this.singletonObjects.get(beanName);if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){同步(this.singletonObjects){if(singletonObject==null&&allowEarlyReference){ObjectFactorysingletonFactory=this.singletonFactories.get(beanName);if(singletonFactory!=null){唱letonObject=singletonFactory.getObject();}}}}returnssingletonObject;}/***判断bean是否正在初始化*/publicbooleanisSingletonCurrentlyInCreation(StringbeanName){returnthis.singletonsCurrentlyInCreation.contains(beanName);}}测试一波publicstaticvoidmain(String[]args)throwsException{DependencyDemodenityDemo=newDependencyDemo();//假装扫描出对象Class[]classes={A.class,B.class};//假装为(ClassaClass:classes)初始化所有bean{dependencyDemo.getBean(aClass);}//trueSystem.out.println(dependencyDemo.getBean(B.class).getA()==dependencyDemo.getBean(A.class));//trueSystem.out.println(dependencyDemo.getBean(A.class).getB()==dependencyDemo.getBean(B.class));}是不是很简单?我们只用了2个map就得到了Spring的循环依赖。使用3个地图怎么样?原因其实很简单。当我们根据BeanName从singletonFactories中获取对应的ObjectFactory,然后调用getObject()方法返回对应的Bean在我们的例子中,ObjectFactory的实现很简单,就是直接返回实例化的对象,但是在Spring中就没那么简单了,执行过程比较复杂。为了避免每次获取ObjectFactory然后调用getObject(),我们可以直接缓存ObjectFactory创建的对象,这样可以提高效率。比如A依赖B和C,B和C又依赖A,如果不缓存,那么B和C的初始化会调用A对应的ObjectFactory的getObject()方法。如果做缓存,只需要调用一次B或C。知道思路了,我们改一下上面的代码,加个缓存。publicclassDependencyDemo{//InitializedBeanprivatefinalMapsingletonObjects=newConcurrentHashMap<>(256);//正在初始化的Bean对应的工厂,此时对象已经被实例化privatefinalMap>singletonFactories=newHashMap<>(16);//缓存BeanprivatefinalMapearlySingletonObjects=newHashMap<>(16);//缓存正在初始化的Bean,放在对象实例化之前privatefinalSetsingletonsCurrentlyInCreation=Collections.newSetFromMap(newConcurrentHashMap<>(16));publicTgetBean(ClassbeanClass)throwsException{//类名是Bean的名字StringbeanName=beanClass.getSimpleName();//已经初始化,或者正在初始化ObjectinitObj=getSingleton(beanName,true);if(initObj!=null){return(T)initObj;}//bean正在初始化singletonsCurrentlyInCreation.add(beanName);//实例化beanObjectobject=beanClass.getDeclaredConstructor().newInstance();singletonFactories.put(beanName,()->{returnobject;});//开始初始化bean,即填充属性Field[]fields=object.getClass().getDeclaredFields();for(Fieldfield:fields){field.setAccessible(true);//获取需要注输入段的classClassfieldClass=field.getType();field.set(object,getBean(fieldClass));}singletonObjects.put(beanName,object);singletonsCurrentlyInCreation.remove(beanName);earlySingletonObjects.remove(beanName);return(T)object;}/***allowEarlyReference参数的含义是Spring是否允许循依赖,默认为true*/publicObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ObjectsingletonObject=this.singletonObjects.get(beanName);if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){synchronized(this.singletonObjects){singletonObject=this.earlySingletonObjects.get(beanName);if(singletonObject==null&&allowEarlyReference){ObjectFactorysingletonFactory=this.singletonFactories.get(beanName);if(singletonFactory!=null){singletonObject=singletonFactory.getObject();this.earlySingletonObjects.put(beanName,singletonObject);this.singletonFactories。remove(beanName);}}}}returnssingletonObject;}}我们写的getSingleton的实现和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String,boolean)的实现完全一样。几乎所有分析Spring循环依赖的文章都会提到这个方法,这次你就明白工作原理是什么了。获取bean时,先从singletonObjects(一级缓存)中获取。如果获取不到,对象正在创建,就从earlySingletonObjects(二级缓存)中获取。如果还是获取不到,就从singletonFactories(三级缓存)中获取,然后将获取到的对象放入earlySingletonObjects(二级缓存)中,清除bean对应的singletonFactories(三级缓存)。bean初始化后放入singletonObjects(一级缓存),清除bean对应的earlySingletonObjects(二级缓存)