范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

请展开说说,SpringBeanIoCAOP循环依赖

  作者:小傅哥
  博客:https://bugstack.cn -  包含: Java 基础,面经手册,Netty4.x,手写Spring,用Java实现JVM,重学Java设计模式,SpringBoot中间件开发,IDEA插件开发,DDD系统架构项目开发,字节码编程...
  沉淀、分享、成长,让自己和他人都能有所收获! 一、前言
  延迟满足能给你带来什么?
  大学有四年时间,但几乎所有人都是临近毕业才发现找一份好工作费劲,尤其是我能非常熟悉的软件开发行业,即使是毕业了还需要额外花钱到培训机构,在学一遍编程技术才能出去找工作。 好像在校这几年压根就没学到什么!
  就我个人而言可能是因为上学期间喜欢编程,也从师哥、师姐那里听到一些关于毕业后找工作的不容易,也了解了一些社会上对程序员开发技能的要求级别。也就是得到了这些消息,又加上自己乐于折腾,我给自己定了一个每天都能完成的小目标: 红尘世界几个王,我自不服迎头上。 日敲代码两百行,冲进世界五百强。
  哈哈哈 ,就这么每天两百行代码,一个月就是6千行,一年就是6万行,三年后开始实习就有18万行,一个应届实习生有将近20万行代码的敲击量,几乎已经可以非常熟练的完成各类简单的工作,在加上实习中对整个项目流程真正的锻炼后,找一个 正经 的开发工作,还是很容易的。
  而这时候找工作的容易,就来自于你一直以来的学习和沉淀,但如果你没经过这些努力,可能等毕业后就会变得非常慌乱,最后没办法只能去一些机构再学习一遍。 二、面试题
  谢飞机,小记! ,以前感觉Spring没啥,看过一篇getBean,我的天!
  谢飞机 :面试官,最近我看了 Spring 的 getBean 发现这里好多东西,还有一个是要解决循环依赖的,这玩意面试有啥要问的吗?
  面试官 :有哇,Spring 是如何解决循环依赖的?
  谢飞机 :嗯,通过三级缓存提前暴露对象解决的。
  面试官 :可以哈,那这三个缓存里都存放了什么样的对象信息呢?
  谢飞机 :一级缓存存放的是完整对象,也叫成品对象。二级缓存存放的是半成品对象,就是那些属性还没赋值的对象。三级缓存存放的是  ObjectFactory<?>  类型的 lambda 表达式,就是这用于处理 AOP 循环依赖的。
  面试官 :可以呀,谢飞机有所准备嘛!那如果没有三级缓存,只有二级或者一级,能解决循环依赖吗?
  谢飞机 :其实我看过资料了,可以解决,只不过 Spring 要保证几个事情,只有一级缓存处理流程没法拆分,复杂度也会增加,同时半成品对象可能会有空指针异常。而将半成品与成品对象分开,处理起来也更加优雅、简单、易扩展。另外 Spring 的两大特性中不仅有 IOC 还有 AOP,也就是基于字节码增强后的方法,该存放到哪,而三级缓存最主要,要解决的循环依赖就是对 AOP 的处理,但如果把 AOP 代理对象的创建提前,那么二级缓存也一样可以解决。但是,这就违背了 Spring 创建对象的原则,Spring 更喜欢把所有的普通 Bean 都初始化完成,在处理代理对象的初始化。
  面试官 :飞机,不错嘛,这次了解了不少。那问个简单的,你撸过循环依赖的解决方案?
  谢飞机 :哦哦,这没有,没实践过!!!确实应该搞一下,试试。 三、什么是循环依赖?1. 问题描述
  了解问题的本质再分析问题,往往更利于对问题有更深入的了解和研究。所以我们在分析 Spring 关于循环依赖的源码之前,先要了解下什么是循环依赖。
  循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖。 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。 所以 Spring 提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话该怎么解决。 2. 问题体现public class ABTest {      public static void main(String[] args) {         new ClazzA();     }  }  class ClazzA {      private ClazzB b = new ClazzB();  }  class ClazzB {      private ClazzA a = new ClazzA();  } 这段代码就是循环依赖最初的模样,你中有我,我中有你,运行就报错  java.lang.StackOverflowError 这样的循环依赖代码是没法解决的,当你看到 Spring 中提供了 get/set 或者注解,这样之所以能解决,首先是进行了一定的解耦。让类的创建和属性的填充分离,先创建出半成品Bean,再处理属性的填充,完成成品Bean的提供。 3. 问题处理
  在这部分的代码中就一个核心目的,我们来自己解决一下循环依赖,方案如下: public class CircleTest {      private final static Map singletonObjects = new ConcurrentHashMap<>(256);      public static void main(String[] args) throws Exception {         System.out.println(getBean(B.class).getA());         System.out.println(getBean(A.class).getB());     }      private static  T getBean(Class beanClass) throws Exception {         String beanName = beanClass.getSimpleName().toLowerCase();         if (singletonObjects.containsKey(beanName)) {             return (T) singletonObjects.get(beanName);         }         // 实例化对象入缓存         Object obj = beanClass.newInstance();         singletonObjects.put(beanName, obj);         // 属性填充补全对象         Field[] fields = obj.getClass().getDeclaredFields();         for (Field field : fields) {             field.setAccessible(true);             Class<?> fieldClass = field.getType();             String fieldBeanName = fieldClass.getSimpleName().toLowerCase();             field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));             field.setAccessible(false);         }         return (T) obj;     }  }  class A {      private B b;      // ...get/set }  class B {     private A a;    // ...get/set } 这段代码提供了 A、B 两个类,互相有依赖。但在两个类中的依赖关系使用的是 setter 的方式进行填充。也就是只有这样才能避免两个类在创建之初不非得强依赖于另外一个对象。 getBean ,是整个解决循环依赖的核心内容,A 创建后填充属性时依赖 B,那么就去创建 B,在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存到singletonObjects  中了,所以 B 可以正常创建,在通过递归把 A 也创建完整了。四、源码分析1. 说说细节
  通过上面的例子我们大概了解到,A和B互相依赖时,A创建完后填充属性B,继续创建B,再填充属性A时就可以从缓存中获取了,如下:
  那这个解决事循环依赖的事放到 Spring 中是什么样呢?展开细节!
  虽然 ,解决循环依赖的核心原理一样,但要放到支撑起整个 Spring 中 IOC、AOP 特性时,就会变得复杂一些,整个处理 Spring 循环依赖的过程如下;
  以上就是关于 Spring 中对于一个有循环依赖的对象获取过程,也就是你想要的 说说细节 乍一看是挺多流程,但是这些也基本是你在调试代码时候必须经过的代码片段,拿到这份执行流程,再调试就非常方便了。 2. 处理过程
  关于本章节涉及到的案例源码分析,已更新到 github:https://github.com/fuzhengwei/interview - interview-31
  以下是单元测试中对AB依赖的获取Bean操作,重点在于进入 getBean 的源码跟进; @Test public void test_alias() {     BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");     Bean_A bean_a = beanFactory.getBean("bean_a", Bean_A.class);     logger.info("获取 Bean 通过别名:{}", bean_a.getBean_b()); }
  org.springframework.beans.factory.support.AbstractBeanFactory.java @Override public  T getBean(String name, Class requiredType) throws BeansException {  return doGetBean(name, requiredType, null, false); } 从 getBean 进入后,获取 bean 的操作会进入到 doGetBean。 之所以这样包装一层,是因为 doGetBean 有很多不同入参的重载方法,方便外部操作。
  doGetBean 方法 protected  T doGetBean(   final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)   throws BeansException {     // 从缓存中获取 bean 实例  Object sharedInstance = getSingleton(beanName);      // mbd.isSingleton() 用于判断 bean 是否是单例模式    if (mbd.isSingleton()) {      // 获取 bean 实例     sharedInstance = getSingleton(beanName, new ObjectFactory() {      @Override      public Object getObject() throws BeansException {       try {         // 创建 bean 实例,createBean 返回的 bean 实例化好的        return createBean(beanName, mbd, args);       }       catch (BeansException ex) {        destroySingleton(beanName);        throw ex;       }      }     });     // 后续的处理操作     bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);    }      // ...    // 返回 bean 实例  return (T) bean; } 按照在源码分析的流程图中可以看到,这一部分是从 getSingleton 先判断是否有实例对象,对于第一次进入是肯定没有对象的,要继续往下走。 在判断 mbd.isSingleton() 单例以后,开始使用基于 ObjectFactory 包装的方式创建 createBean,进入后核心逻辑是开始执行 doCreateBean 操作。
  doCreateBean 方法 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)   throws BeanCreationException {      // 创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回   instanceWrapper = createBeanInstance(beanName, mbd, args);     // 添加 bean 工厂对象到 singletonFactories 缓存中   addSingletonFactory(beanName, new ObjectFactory() {    @Override    public Object getObject() throws BeansException {      // 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 bean。     return getEarlyBeanReference(beanName, mbd, bean);    }   });     try {    // 填充属性,解析依赖关系   populateBean(beanName, mbd, instanceWrapper);   if (exposedObject != null) {    exposedObject = initializeBean(beanName, exposedObject, mbd);   }  }    // 返回 bean 实例  return exposedObject; } 在 doCreateBean 方法中包括的内容较多,但核心主要是创建实例、加入缓存以及最终进行属性填充,属性填充就是把一个 bean 的各个属性字段涉及到的类填充进去。 createBeanInstance ,创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回addSingletonFactory ,添加 bean 工厂对象到 singletonFactories 缓存中getEarlyBeanReference ,获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 bean。populateBean ,填充属性,解析依赖关系。也就是从这开始去找寻 A 实例中属性 B,紧接着去创建 B 实例,最后在返回回来。
  getSingleton 三级缓存 protected Object getSingleton(String beanName, boolean allowEarlyReference) {   // 从 singletonObjects 获取实例,singletonObjects 是成品 bean  Object singletonObject = this.singletonObjects.get(beanName);  // 判断 beanName ,isSingletonCurrentlyInCreation 对应的 bean 是否正在创建中  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {   synchronized (this.singletonObjects) {     // 从 earlySingletonObjects 中获取提前曝光未成品的 bean    singletonObject = this.earlySingletonObjects.get(beanName);    if (singletonObject == null && allowEarlyReference) {      // 获取相应的 bean 工厂     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);     if (singletonFactory != null) {       // 提前曝光 bean 实例,主要用于解决AOP循环依赖      singletonObject = singletonFactory.getObject();            // 将 singletonObject 放入缓存中,并将 singletonFactory 从缓存中移除      this.earlySingletonObjects.put(beanName, singletonObject);      this.singletonFactories.remove(beanName);     }    }   }  }  return (singletonObject != NULL_OBJECT ? singletonObject : null); } singletonObjects.get(beanName) ,从 singletonObjects 获取实例,singletonObjects 是成品 beanisSingletonCurrentlyInCreation ,判断 beanName ,isSingletonCurrentlyInCreation 对应的 bean 是否正在创建中allowEarlyReference ,从 earlySingletonObjects 中获取提前曝光未成品的 beansingletonFactory.getObject() ,提前曝光 bean 实例,主要用于解决AOP循环依赖
  综上 ,是一个处理循环依赖的代码流程,这部分提取出来的内容主要为核心内容,并没与长篇大论的全部拆取出来,大家在调试的时候会涉及的比较多,尽可能要自己根据流程图操作调试几遍。 3. 依赖解析
  综上从我们自己去尝试解决循环依赖,学习了循环依赖的核心解决原理。又分析了 Spring 解决的循环依赖的处理过程以及核心源码的分析。那么接下来我们在总结下三级缓存分别不同的处理过程,算是一个总结,也方便大家理解。 1. 一级缓存能解决吗?
  其实只有一级缓存并不是不能解决循环依赖,就像我们自己做的例子一样。 但是在 Spring 中如果像我们例子里那么处理,就会变得非常麻烦,而且也可能会出现 NPE 问题。 所以如图按照 Spring 中代码处理的流程,我们去分析一级缓存这样存放成品 Bean 的流程中,是不能解决循环依赖的问题的。因为 A 的成品创建依赖于 B,B的成品创建又依赖于 A,当需要补全B的属性时 A 还是没有创建完,所以会出现死循环。 2. 二级缓存能解决吗?
  有了二级缓存其实这个事处理起来就容易了,一个缓存用于存放成品对象,另外一个缓存用于存放半成品对象。 A 在创建半成品对象后存放到缓存中,接下来补充 A 对象中依赖 B 的属性。 B 继续创建,创建的半成品同样放到缓存中,在补充对象的 A 属性时,可以从半成品缓存中获取,现在 B 就是一个完整对象了,而接下来像是递归操作一样 A 也是一个完整对象了。 3. 三级缓存解决什么?
  有了二级缓存都能解决 Spring 依赖了,怎么要有三级缓存呢。其实我们在前面分析源码时也提到过,三级缓存主要是解决 Spring AOP 的特性。AOP 本身就是对方法的增强,是  ObjectFactory<?>  类型的 lambda 表达式,而 Spring 的原则又不希望将此类类型的 Bean 前置创建,所以要存放到三级缓存中处理。其实整体处理过程类似,唯独是 B 在填充属性 A 时,先查询成品缓存、再查半成品缓存,最后在看看有没有单例工程类在三级缓存中。最终获取到以后调用 getObject 方法返回代理引用或者原始引用。 至此也就解决了 Spring AOP 所带来的三级缓存问题。 本章节涉及到的 AOP 依赖有源码例子,可以进行调试 五、总结回顾本文基本以实际操作的例子开始,引导大家对循环依赖有一个整体的认识,也对它的解决方案可以上手的例子,这样对后续的关于 Spring 对循环依赖的解决也就不会那么陌生了。 通篇全文下来大家也可以看到,三级缓存并不是非必须不可,只不过在满足 Spring 自身创建的原则下,是必须的。如果你可以下载 Spring 源码对这部分代码进行改动下,提前创建 AOP 对象保存到缓存中,那么二级缓存一样可以解决循环依赖问题。 关于循环依赖可能并不是一个好的编码方式,如果在自己的程序中还是要尽可能使用更合理的设计模式规避循环依赖,可能这些方式会增加代码量,但在维护上会更加方便。 当然这不是强制,可以根据你的需要而来。
那些可儿们何时能回归自己的家园我在苍穹轻盈流转遥望高山之巅尘作者李菲(西禺)推开窗,看见精灵在空中飞旋细小,又闪烁耀眼我张开手想留它们于指尖阳光下灿烂成长的是心动的舞曲和醉梦前尘的翩跹我闭上眼,仿佛生出洁白坚实的翅膀托我直上彩云之间那些可中央为发展浙江,选定宁波为长三角南翼中心城市,为何选中宁波?宁波,位于浙江省的浙东地区,长三角经济区的核心区,与长三角领头羊城市上海隔海相望。为了发展浙东地区,中央在关于批复宁波市城市总体规划的通知中,确定宁波市为长江三角南翼经济中心城市。粮食涨价冲击养殖业,两会委员代表建议这样开源节流随着粮食价格上涨,国内养殖行业压力激增。从2022年初开始,国内豆粕玉米等饲料用粮价格大幅上涨,也让奶牛生猪等养殖成本快速上升。在3月2日举行的媒体沟通会上,全国政协委员新希望集团湖南医药学院举行第一期青年马克思主义者骨干培训班开班仪式2月26日上午,湖南医药学院第一期青年马克思主义者骨干培训班开班仪式在至善楼一楼东头会议室举行。湖南医药学院党委书记张在其副校长罗求实宣传统战部部长蒲洋出席,第一期青年马克思主义者老字号创新玩出云花样精耕工艺,改进品质,借助网络平台老字号创新玩出云花样(网上中国)天津市中华老字号餐饮企业狗不理集团转型互联网技术创新新兴业态,探索直播间下单送餐到家河南洛阳高家三彩直播带货等创意营福建美女市长落马政绩垫底靠马屁升迁,女儿结婚礼金几百万在福建有这么一位叫朱仁秀的女市长,她出身于平民家庭却不甘平庸,一路拼搏考上了莘莘学子们梦寐以求的中央党校。可这个昔日让所有人都看好的考场强者,却在日后逐渐走向了贪污腐败的道路。任职麋鹿的前世今生麋鹿传奇展向大众开放3月2日,在第十个世界动植物日来临之际,由北京麋鹿生态实验中心北京生物多样性研究中心北京南海子麋鹿苑博物馆策划制作的麋鹿传奇展向社会大众开放,展览从麋鹿自然演化麋鹿科学保护麋鹿文化揭秘伟人唯一曾孙,19岁帅气酷似祖辈,高考650分上中国人民大学2022年6月,全国夏季高考热火朝天地开幕落幕,不久,各省的高考成绩便在十几亿人民的翘首以待中,新鲜出炉了。一个19岁的男生以650分的佳绩,在人才济济竞争激烈的北京考场中脱颖而出新能源车置换补贴后天起开始申领我省相关政策实施细则出炉新能源车置换补贴后天起开始申领我省相关政策实施细则出炉符合条件者可享受5000元补贴,弄虚作假将列入失信名单记者3月2日从湖南省商务厅获悉,湖南省2023年新能源汽车置换补贴政策实松雅湖收费站三保三大初见成效打扫场部卫生。提升收费服务。红网时刻新闻通讯员雷慧敏长沙报道近日,按照湖南高速集团三保三大专项行动要求,松雅湖收费站迅速积极响应,经过半个月的持续行动,初见成效。净化环境,提升形象3月3日猪价陷入僵持!下跌风暴席卷21地,飘绿时代来临?3月开门红后,立马迎来了下跌风暴,惊醒广大养殖户的美梦!据猪好多数据监测显示,今日全国外三元生猪均价约为15。91元公斤,与昨日对比下跌约为0。05元公斤。当前,19地生猪价格上涨
国家报佛罗伦萨有意皮纳蒙蒂,国米标价2000万欧可先租后买直播吧6月6日讯据国家报报道称,佛罗伦萨有意引进皮纳蒙蒂,国米要价2000万欧但可以接受先租后买。佛罗伦萨正在为下赛季寻找一名新的前锋,本赛季他们在比赛中创造了许多机会却没能把握住福建省防指部署持续性强降雨防御工作来源台海网台海网6月6日讯据福建日报报道5日,省防指召开全省视频会议,部署持续性强降雨防御工作。省委常委常务副省长郭宁宁出席并讲话。会议强调,本轮强降雨持续时间长累计雨量大短时强度群众替俄罗斯担心拖不起,但俄罗斯在乌发展地方武装就不一样了群众最替俄罗斯担心的是拖字诀战术。但是俄罗斯在乌东地区发展地方武装,这样就不一样了。俄罗斯这样打是对的。那么下一步我们要看的戏就是乌克兰这只顽猴什么时候被驯服?什么时候受控?不过,陈乔恩金瀚主演的遇见璀璨的你预计定档6月15日播出偶像剧女王芒果三女将之一的陈乔恩新剧遇见璀璨的你即将在6月见面了。由企鹅影视自制,陈铭章导演。陈乔恩金瀚陈宥维王子璇邹廷威等主演的45集都市职场情感剧遇见璀璨的你预计定档6月15日作为一个娇蛮公主,得在宫里横着走,不然怎么体现我尊贵的身份我是当今圣上最宠爱的公主。没错,皇帝是我爹,皇后是我娘,并且帝后恩爱。我呢,作为一个满分的娇蛮公主,那我不得在宫里横着走,不然怎么体现我尊贵的身份!确实,最近我在练习横着走。御花园通胀达40年新高,美国想白菜价进口俄石油,如意算盘会成功吗?美国物价高涨,打算白菜价进口俄罗斯石油。总统财长接连发声,美联储收割世界自酿的苦果才刚刚开始。怪事年年有,美国特别多。近日,美国财长耶伦,公开认错,表示美国经济形势如此严峻,通胀率女士发型有很多,最火的还是这3种,立体瘦脸很独特既然你我相遇,那就是缘分,关注时尚,关注发型,关注刘丽丽随着人们审美的多元化,发型的款式也是越来越多,尤其是女士发型,从短发到长发,让你月月换都能不重样,虽然女士发型有很多,但是最上错花轿嫁对郎,重温经典还是很香剧荒的时候,想起小时候看过的一部剧,无论剧情还是演技,放到今天依然是内地古装偶像剧的天花板级别,演员的颜值与扮相绝对不输现在的一众花花草草。你还记得这部上错花轿嫁对郎吗?2001年花330i的钱买了个大众CC,车主是人傻钱多吗?去年秋天抄底提了台2021款CC,标价4。4万美元的中配版,全款落地才花了4万美元(当时折合人民币26万元)。如今开了半年多,是时候写写评价了。购车经历这台车是我在美国长租的,首付炊事班的故事播出20年演员现状有人二婚,有人退圈无音信如今的娱乐圈真的是爱豆没舞台,演员没演技,导演没作品。其实早年并不缺优秀的制作人和导演。小编这几天得知了著名制作人廉振华先生逝世的消息。也许大家并不了解他,但是你一定看过他的作品。福建京剧院开启京彩一夏京剧传统经典演出季端午假期,作为福建京剧院京彩一夏京剧传统经典演出季的首场演出,由国家二级演员郑钰领衔主演的穆桂英大战洪州在福州凤凰剧院上演。舞台上的穆桂英头戴雉尾,背扎长靠,跨马持枪,驰骋疆场,唱