集美阅读大全是一个以文章句子为主题的在线阅读网站。内含有各种经典好文章,爱情美文,诗歌散文,情感句子说说,范文资料等。读好文章,尽在集美阅读大全!!!
当前位置:集美阅读大全 >杂文 > 正文

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

摘要:

若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。 Spring 不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘 Spring 循环依赖的最本质原因。

Spring循环依赖流程图

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

Spring循环依赖发生原因

  • 使用了具有代理特性的BeanPostProcessor
  • 典型的有 事务注解@Transactional,异步注解@Async等 

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

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

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

源码分析揭秘

  1. protected Object doCreateBean( ... ){ 
  2.         ... 
  3.         boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); 
  4.         if (earlySingletonExposure) { 
  5.             addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 
  6.         } 
  7.         ... 
  8.      
  9.         // populateBean这一句特别的关键,它需要给A的属性赋值,所以此处会去实例化B~~ 
  10.         // 而B我们从上可以看到它就是个普通的Bean(并不需要创建代理对象),实例化完成之后,继续给他的属性A赋值,而此时它会去拿到A的早期引用 
  11.         // 也就在此处在给B的属性a赋值的时候,会执行到上面放进去的Bean A流程中的getEarlyBeanReference()方法  从而拿到A的早期引用~~ 
  12.         // 执行A的getEarlyBeanReference()方法的时候,会执行自动代理创建器,但是由于A没有标注事务,所以最终不会创建代理,so B合格属性引用会是A的**原始对象** 
  13.         // 需要注意的是:@Async的代理对象不是在getEarlyBeanReference()中创建的,是在postProcessAfterInitialization创建的代理 
  14.         // 从这我们也可以看出@Async的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~) 
  15.      
  16.         // 结论:此处给A的依赖属性字段B赋值为了B的实例(因为B不需要创建代理,所以就是原始对象) 
  17.         // 而此处实例B里面依赖的A注入的仍旧为Bean A的普通实例对象(注意  是原始对象非代理对象)  注:此时exposedObject也依旧为原始对象 
  18.         populateBean(beanName, mbd, instanceWrapper); 
  19.          
  20.         // 标注有@Async的Bean的代理对象在此处会被生成~~~ 参照类:AsyncAnnotationBeanPostProcessor 
  21.         // 所以此句执行完成后  exposedObject就会是个代理对象而非原始对象了 
  22.         exposedObject = initializeBean(beanName, exposedObject, mbd); 
  23.          
  24.         ... 
  25.         // 这里是报错的重点~~~ 
  26.         if (earlySingletonExposure) { 
  27.             // 上面说了A被B循环依赖进去了,所以此时A是被放进了二级缓存的,所以此处earlySingletonReference 是A的原始对象的引用 
  28.             // (这也就解释了为何我说:如果A没有被循环依赖,是不会报错不会有问题的   因为若没有循环依赖earlySingletonReference =null后面就直接return了) 
  29.             Object earlySingletonReference = getSingleton(beanName, false); 
  30.             if (earlySingletonReference != null) { 
  31.                 // 上面分析了exposedObject 是被@Aysnc代理过的对象, 而bean是原始对象 所以此处不相等  走else逻辑 
  32.                 if (exposedObject == bean) { 
  33.                     exposedObject = earlySingletonReference; 
  34.                 } 
  35.                 // allowRawInjectionDespiteWrapping 标注是否允许此Bean的原始类型被注入到其它Bean里面,即使自己最终会被包装(代理) 
  36.                 // 默认是false表示不允许,如果改为true表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~ 
  37.                 // 另外dependentBeanMap记录着每个Bean它所依赖的Bean的Map~~~~ 
  38.                 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { 
  39.                     // 我们的Bean A依赖于B,so此处值为["b"] 
  40.                     String[] dependentBeans = getDependentBeans(beanName); 
  41.                     Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); 
  42.      
  43.                     // 对所有的依赖进行一一检查~    比如此处B就会有问题 
  44.                     // “b”它经过removeSingletonIfCreatedForTypeCheckOnly最终返返回false  因为alreadyCreated里面已经有它了表示B已经完全创建完成了~~~ 
  45.                     // 而b都完成了,所以属性a也赋值完成儿聊 但是B里面引用的a和主流程我这个A竟然不相等,那肯定就有问题(说明不是最终的)~~~ 
  46.                     // so最终会被加入到actualDependentBeans里面去,表示A真正的依赖~~~ 
  47.                     for (String dependentBean : dependentBeans) { 
  48.                         if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { 
  49.                             actualDependentBeans.add(dependentBean); 
  50.                         } 
  51.                     } 
  52.          
  53.                     // 若存在这种真正的依赖,那就报错了~~~  则个异常就是上面看到的异常信息 
  54.                     if (!actualDependentBeans.isEmpty()) { 
  55.                         throw new BeanCurrentlyInCreationException(beanName, 
  56.                                 "Bean with name '" + beanName + "' has been injected into other beans [" + 
  57.                                 StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + 
  58.                                 "] in its raw version as part of a circular reference, but has eventually been " + 
  59.                                 "wrapped. This means that said other beans do not use the final version of the " + 
  60.                                 "bean. This is often the result of over-eager type matching - consider using " + 
  61.                                 "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); 
  62.                     } 
  63.                 } 
  64.             } 
  65.         } 
  66.         ... 
  67.     } 

问题简化

  • 发生循环依赖时候 Object earlySingletonReference = getSingleton(beanName, false); 肯定有值
  • 缓存工厂 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 将给实例对象添加 SmartInstantiationAwareBeanPostProcessor
  • AbstractAutoProxyCreator 是 SmartInstantiationAwareBeanPostProcessor 的子类,一定记住了,一定记住, SmartInstantiationAwareBeanPostProcessor 的子类很关键!!!!!
  • exposedObject = initializeBean(beanName, exposedObject, mbd); 进行 BeanPostProcessor后置处理,注意是 BeanPostProcessor !!!!!

Spring 的循环依赖被它的三级缓存给轻易解决了,但是这2个地方的后置处理带来了 循环依赖的问题。

对比AbstractAdvisorAutoProxyCreator和AsyncAnnotationBeanPostProcessor

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

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

由于 SmartInstantiationAwareBeanPostProcessor 的子类会在两处都会执行后置处理,所以前后都会相同的对象引用,不会发生循环依赖问题,异步注解就不行了 ,至于为什么?自己看上面的分析,仔细看哦!

如何解决循环依赖?

  • 改变加载顺序
  • @Lazy 注解
  • allowRawInjectionDespiteWrapping 设置为 true (利用了判断的那条语句)
  • 别使用相关的 BeanPostProcessor 设计到的注解,,哈哈 这不太现实。 
     

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

@Lazy

@Lazy 一般含义是懒加载,它只会作用于 BeanDefinition.setLazyInit() 。而此处给它增加了一个能力:延迟处理(代理处理)

  1. // @since 4.0 出现得挺晚,它支持到了@Lazy  是功能最全的AutowireCandidateResolver 
  2.     public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver { 
  3.         // 这是此类本身唯一做的事,此处精析  
  4.         // 返回该 lazy proxy 表示延迟初始化,实现过程是查看在 @Autowired 注解处是否使用了 @Lazy = true 注解  
  5.         @Override 
  6.         @Nullable 
  7.         public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { 
  8.             // 如果isLazy=true  那就返回一个代理,否则返回null 
  9.             // 相当于若标注了@Lazy注解,就会返回一个代理(当然@Lazy注解的value值不能是false) 
  10.             return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); 
  11.         } 
  12.      
  13.         // 这个比较简单,@Lazy注解标注了就行(value属性默认值是true) 
  14.         // @Lazy支持标注在属性上和方法入参上~~~  这里都会解析 
  15.         protected boolean isLazy(DependencyDescriptor descriptor) { 
  16.             for (Annotation ann : descriptor.getAnnotations()) { 
  17.                 Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); 
  18.                 if (lazy != null && lazy.value()) { 
  19.                     return true
  20.                 } 
  21.             } 
  22.             MethodParameter methodParam = descriptor.getMethodParameter(); 
  23.             if (methodParam != null) { 
  24.                 Method method = methodParam.getMethod(); 
  25.                 if (method == null || void.class == method.getReturnType()) { 
  26.                     Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); 
  27.                     if (lazy != null && lazy.value()) { 
  28.                         return true
  29.                     } 
  30.                 } 
  31.             } 
  32.             return false
  33.         } 
  34.      
  35.         // 核心内容,是本类的灵魂~~~ 
  36.         protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) { 
  37.             Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory, 
  38.                     "BeanFactory needs to be a DefaultListableBeanFactory"); 
  39.      
  40.             // 这里毫不客气的使用了面向实现类编程,使用了DefaultListableBeanFactory.doResolveDependency()方法~~~ 
  41.             final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); 
  42.      
  43.             //TargetSource 是它实现懒加载的核心原因,在AOP那一章节了重点提到过这个接口,此处不再叙述 
  44.             // 它有很多的著名实现如HotSwappableTargetSource、SingletonTargetSource、LazyInitTargetSource、 
  45.             //SimpleBeanTargetSource、ThreadLocalTargetSource、PrototypeTargetSource等等非常多 
  46.             // 此处因为只需要自己用,所以采用匿名内部类的方式实现~~~ 此处最重要是看getTarget方法,它在被使用的时候(也就是代理对象真正使用的时候执行~~~) 
  47.             TargetSource ts = new TargetSource() { 
  48.                 @Override 
  49.                 public Class<?> getTargetClass() { 
  50.                     return descriptor.getDependencyType(); 
  51.                 } 
  52.                 @Override 
  53.                 public boolean isStatic() { 
  54.                     return false
  55.                 } 
  56.          
  57.                 // getTarget是调用代理方法的时候会调用的,所以执行每个代理方法都会执行此方法,这也是为何doResolveDependency 
  58.                 // 我个人认为它在效率上,是存在一定的问题的~~~所以此处建议尽量少用@Lazy~~~    
  59.                 //不过效率上应该还好,对比http、序列化反序列化处理,简直不值一提  所以还是无所谓  用吧 
  60.                 @Override 
  61.                 public Object getTarget() { 
  62.                     Object target = beanFactory.doResolveDependency(descriptor, beanName, nullnull); 
  63.                     if (target == null) { 
  64.                         Class<?> type = getTargetClass(); 
  65.                         // 对多值注入的空值的友好处理(不要用null) 
  66.                         if (Map.class == type) { 
  67.                             return Collections.emptyMap(); 
  68.                         } else if (List.class == type) { 
  69.                             return Collections.emptyList(); 
  70.                         } else if (Set.class == type || Collection.class == type) { 
  71.                             return Collections.emptySet(); 
  72.                         } 
  73.                         throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(), 
  74.                                 "Optional dependency not present for lazy injection point"); 
  75.                     } 
  76.                     return target; 
  77.                 } 
  78.                 @Override 
  79.                 public void releaseTarget(Object target) { 
  80.                 } 
  81.             };    
  82.      
  83.             // 使用ProxyFactory  给ts生成一个代理 
  84.             // 由此可见最终生成的代理对象的目标对象其实是TargetSource,而TargetSource的目标才是我们业务的对象 
  85.             ProxyFactory pf = new ProxyFactory(); 
  86.             pf.setTargetSource(ts); 
  87.             Class<?> dependencyType = descriptor.getDependencyType(); 
  88.              
  89.             // 如果注入的语句是这么写的private AInterface a;  那这类就是借口 值是true 
  90.             // 把这个接口类型也得放进去(不然这个代理都不属于这个类型,反射set的时候岂不直接报错了吗????) 
  91.             if (dependencyType.isInterface()) { 
  92.                 pf.addInterface(dependencyType); 
  93.             } 
  94.             return pf.getProxy(beanFactory.getBeanClassLoader()); 
  95.         } 
  96.     } 

标注有 @Lazy 注解完成注入的时候,最终注入只是一个此处临时生成的代理对象,只有在真正执行目标方法的时候才会去容器内拿到真是的 bean 实例来执行目标方法。

利用allowRawInjectionDespiteWrapping属性来强制改变判断

  1. @Component 
  2.     public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 
  3.         @Override 
  4.         public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
  5.             ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true); 
  6.         } 
  7.     } 

这样会导致容器里面的是代理对象,暴露给其他实例的是原始引用,导致不生效了。由于它只对循环依赖内的 Bean 受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案。

【编辑推荐】

点赞 0

未经允许不得转载:杂烩网 » Springboot源码分析之Spring循环依赖揭秘

课后答案张九龄《望月怀远》阅读答案及全诗翻译赏析

望月怀远张九龄海上生明月,天涯共此时。情人怨遥夜,竟夕起相思。灭烛怜光满,披衣觉露滋。不堪盈手赠,还寝梦佳期。注释⑴怀远:怀念远方的亲人。⑵最前面两句:辽阔无边的大海上升起一轮明月,使人想起了远在天涯……
2023-11-22 04:53暂无评论阅读详情

课后答案王安石《次韵唐公三首其三旅思》阅读答案

次韵唐公三首其三旅思王安石此身南北老,愁见问征途。地大蟠三楚,天低入五湖。看云心共远,步月影同孤。慷慨秋风起,悲歌不为鲈②。注:①张壤,字唐公,北宋嘉佑六年契丹国母生辰使,王安石友人。②《晋书&mid……
2023-11-22 04:52暂无评论阅读详情

笔记心得各级干部学习执法为民心得体会

  &ldquo;各级干部都要牢固树立全心全意为人民服务的思想和真心实意对人民负责的精神,做到心里装着群众,凡事想着群众,工作依靠群众,一切为了群众。要坚持权为民所用,情为民所系,利为民所谋,为群众诚……
2023-11-22 04:12暂无评论阅读详情

笔记心得寒假大学生社会实践心得体会

  自从走进了大学,就业问题就似乎总是围绕在我们的身边,成了说不完的话题。在现今社会,招聘会上的大字报都总写着&ldquo;有经验者优先&rdquo;,可还在校园里面的我们这班学子社会经验又会拥有多少……
2023-11-22 04:08暂无评论阅读详情

协议书济南市某美容院转让协议第2篇

&nbsp;&nbsp;__________美容院根据中华人民共和国国务院劳动法规和________市私营企业劳动管理实施办法,结合本美容院经营的具体所需今制订此劳动合同书。&nbsp;&nbsp;双……
2023-11-22 02:36暂无评论阅读详情

剧本劳模宣传短剧剧本《阿咪也想当劳模》

  1、机械厂门卫处,日,外。  清早,机械厂班长李玉伟开着别克赛欧小汽车驶进厂区,门卫室内的保安一边按开电动门,一边朝李玉伟摆手。  李玉伟:(摇下车窗,笑着打招呼)小秦,早。  保安小秦:(笑着)……
2023-11-22 02:11暂无评论阅读详情

教程灰雀说课稿

灰雀说课稿  灰雀说课稿(一):  《灰雀》说课稿  一、说教材  《灰雀》是义务教育课程标准实验教科书,小学语文第五册第二单元的一篇讲读课文。这篇课文记叙了列宁在莫斯科郊外养病期间爱护灰雀的故事。列……
2023-11-22 00:41暂无评论阅读详情

课件“吴隐之字处默,濮阳鄄城人”阅读答案及原文

吴隐之字处默,濮阳鄄城人。美姿容,善谈论,博涉文史,以儒雅标名。弱冠而介立,有清操,虽儋石无储,不取非其道。事母孝谨,及其执丧,哀毁过礼。与太常韩康伯邻居,康伯母,贤明妇人也,每闻隐之哭声,辍餐投箸,……
2023-11-22 00:38暂无评论阅读详情

标签