首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往
您找到你想要的搜索结果了吗?
是的
没有找到

深度讲解spring的循环依赖以及三级缓存

这样会顺利的得到bean对象 在创建a时首先spring找到a的class,随后操作beandefinition,并推断构造方法->实例化->对象->属性填充->afterPropertiesSet()->AOP(如果没有aop就不会进行该操作,也不会生成代理对象),判断是否有切面表达式操作此类,如果有会创建出a的代理对象,代理对象里的target指向里面真正的属性,最后spring完成创建,把对象放入单例池 在此案例中,a对象在创建的时候会先去单例池找b,但是此时单例池中并没有b对象,这时就会去创建b,但是b对象里也需要a对象属性,这时,spring会去单例池中找a对象,但是a此时还在构造中,还没有创建完成,这样就造成了循环依赖,到底spring是如何解决循环依赖的呢。 解答:运用了三级缓存,也就是三个map,在新版本中,两个concurrent的map,剩下一个都是普通的map,是二级缓存。 在a对象刚刚初始化时(还未完成),会把new出来的a,此时a里面还没有赋值,此时spring就会把a放入一个map中,map的key就是a class的名字,value就是刚创造出来的原始a对象,其实这里放入的是一个lambda表达式,里面除了有a的原始对象,还有bean的定义beandefinition,beanname,这里的三级缓存也不一定用得上,得看a到底有没有进行aop,同样b也是同样的方法把原始b对象和一些bean定义放入一个map中。 这时候,可以解决上面b在创建时找a没有在单例池找到的问题,它会在刚才创造的map中找到一个原始a的对象(注意这里是原始的a对象,这时候a对象的里面的属性是空的,这看上去是一个问题,其实这个问题不是一个问题,因为最终这个a中的对象b是会被赋值的,只是把a中的b对象赋值的这个动作放在了后面【对象引用】),接下来b对象会把剩余的动作做完,把b对象放入单例池中,这样,a就可以在单例池中找到b。【注:这里的单例池也就是一个concurrentmap,属于一级缓存,原始a,b对象放入的map是三级map】 这里会引入一个问题,就是aop,因为一旦a对象里的某个方法被aop切到,这时候放在一级map(单例池SingletonObjects)中的对象是代理对象(cglib),a的代理对象和a的原始对象并不是一个对象,a的代理对象里的target属性指向了a的原始对象,这样就会引出问题,在b对象找到a对象给a对象赋值时应该找到这个代理a对象,但是aop操作在前面讲到是在创建完对象后才会进行的,可是现在的a对象还没有到达这一步,我们只能提前进行aop,只有在某个特殊的情况下需要进行提前aop,这个特殊条件就是当a出现了循环依赖。如何检测a出现了循环依赖呢? 这时候会有一个createingSet(),在创建a之前,就会把该行为记录到这个set中,当a创建完毕后放入一级缓存中时就会移出该值,这时候在上述的b找a时,发现单例池中没有找到a对象,这时候就会去creatingSet中找是否有a对象,这时候就判断出了a和b存在循环依赖,这时候就会提前进行aop,得到a的代理对象,这时候就可以把创建出来的a的代理对象给b中的a赋值,这时候要注意在得到a对象的代理对象时,不能把它放到单例池中,因为这时候的a的代理对象并没有完全的进行完bean的生命周期(不完整的a代理对象),这时候再想一个问题,如果a对象里还有一个c对象属性,c中也调用了a,那么经历了和b一样的过程中,这时候spring又会创建出一个a代理对象,这就不合理了,应该b和c应该是同一个a的代理对象,那么这样就又需要一个map(earlySingletonObjects 二级缓存),这里的map也是key是beanname,value是object,所以这时候要扩充上面的说法,在判断出是循环依赖后,先会去earlySingletonObjects中找是否有,如果没有再创建a的代理对象,在完成所有的其他事情以后,这时候earlySingletonObjects中的=可以取到a的代理对象,此时已经完成了a代理对象的创建,把它再放入到代理池中。 这时候再强调三级缓存的作用,因为a代理对象里的target指向的是a的原始对象,这时候之前的map已经保存了,这就用上了三级缓存。

04

getchar的使用

1.从缓冲区读走一个字符,相当于清除缓冲区 2.前面的scanf()在读取输入时会在缓冲区中留下一个字符’\n’(输入完s[i]的值后按回车键所致),所以如果不在此加一个 getchar()把这个回车符取走的话,gets()就不会等待从键盘键入字符,而是会直接取走这个“无用的”回车符,从而导致读取有误 3. getchar()是在输入缓冲区顺序读入一个字符(包括空格、回车和Tab) getchar()使用不方便,解决方法: (1)使用下面的语句清除回车: while(getchar()!=’\n’); (2)用getche()或getch()代替getchar(),其作用是从键盘读入一个字符(不用按回车),注意要包含头文件<conio.h> 4. getchar()是stdio.h中的库函数,它的作用是从stdin流中读入一个字符,也就是说,如果stdin有数据的话不用输入它就可以直接 读取了,第一次getchar()时,确实需要人工的输入,但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读 取了。 实际上是 输入设备->内存缓冲区->程序getchar 你按的键是放进缓冲区了,然后供程序getchar 你有没有试过按住很多键然后等一会儿会滴滴滴滴响,就是缓冲区满了,你后头按的键没有存进缓冲区. 键盘输入的字符都存到缓冲区内,一旦键入回车,getchar就进入缓冲区读取字符,一次只返回第一个字符作为getchar函数的值,如果 有循环或足够多的getchar语句,就会依次读出缓冲区内的所有字符直到’\n’.要理解这一点,之所以你输入的一系列字符被依次读出 来,是因为循环的作用使得反复利用getchar在缓冲区里读取字符,而不是getchar可以读取多个字符,事实上getchar每次只能读取一 个字符.如果需要取消’\n’的影响,可以用getchar();来清除,这里getchar();只是取得了’\n’但是并没有赋给任何字符变量,所以不 会有影响,相当于清除了这个字符.还要注意的是这里你在键盘上输入ssss看到的回显正是来自于getchar的作用,如果用getch就看不

05
领券