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

请展开说说,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 对象保存到缓存中,那么二级缓存一样可以解决循环依赖问题。 关于循环依赖可能并不是一个好的编码方式,如果在自己的程序中还是要尽可能使用更合理的设计模式规避循环依赖,可能这些方式会增加代码量,但在维护上会更加方便。 当然这不是强制,可以根据你的需要而来。
东北制药良心药火了,被网友赐予人民的制药称号在药价飞涨的今天,东北制药被网友赐予人民的制药的伟大称号。在退烧药紧缺的当下,东北制药秉持良心价,退烧药2元钱一板。20片退烧药,价格才两元钱,这太难能可贵。而为了满足用户需求,东我阳了,重温了一次坐月子,感受太真实当朋友圈不断爆出我阳了,娃他爸阳了,北京,广东阳了吃啥,阳了睡,阳了喝,阳了还要痛。我当时是什么心情呢。我是一直居家办公,所以我就是坚持两点一线,早晚接送娃娃,其余时间都在家。孩子气候践行者用专业与热情守护美好环境在全球气候变化的当下,在遥远的荒野,在人们生活的城市,我们欣喜地看到,有一群闪闪发光的行动者,正在一步一个脚印地,为气候保护积极贡献着自己的专业能力以及青春。他们坚持创新不懈努力的世界杯决赛结束,很失望世界杯决赛表演的痕迹太明显。四年一度的足球狂欢落幕,梅西终于捧起大力神杯,姆巴佩只能黯然离场。这个结果想必是大家都期待的一个结局,为了这个比赛我头一次熬夜到晚上三点,但是看完比赛我阿根廷首发变动迪马利亚顶替帕雷德斯直播吧12月18日讯世界杯决赛,阿根廷vs法国,双方公布首发阵容。阿根廷方面,相较半决赛做出一个人员调整,迪马利亚取代帕雷德斯进先发。决赛首发23马丁内斯26莫利纳13罗梅罗19奥法国首发曝光!4大伤员回归全部在线,烟雾弹太猛,阿根廷真悬了北京时间12月18日,阿根廷和法国的大战即将到来,法国权威媒体队报提前公布了他们的首发阵容!现在这套阵容已经揭晓,后防线上将会是孔德瓦拉内于帕梅卡诺和特奥,中场位置琼阿梅尼拉比奥特世界杯情报阿根廷准备多套阵型,法国队多名主力有伤在身不好!法国的休假时间少于阿根廷一天阿根廷与法国之间的世界杯决赛将会展开。根据之前的四强赛,法国的休息时间要少于阿根廷,阿根廷人在争夺决赛的时候,至少提前24个小时,这对于法国而言并国产LED屏幕助力世界杯!业内人士LED屏幕海外市场需求回暖,新兴市场增幅明显视频加载中(央视财经天下财经)卡塔尔世界杯的足球场上可以看到大量的屏幕,这些LED大屏是观赛球迷获得比赛信息参与互动的主要途径,也是商业投放的重要载体。这其中绝大部分的LED屏幕,狂胜西部第三,威少强轰151112,全队7人上双,湖人表现炸裂NBA常规赛,继续鏖战,主场作战的湖人队迎战约基奇带领的掘金队,湖人队渴望避免连败。湖人队的首发没有变化,本场比赛,浓眉哥和约基奇的对决是比赛的一大看点。比赛开局,湖人队的进攻打得NBA最新排名!灰熊绿军霸榜,欧文绝杀取5连胜,湖人逼近勇士12月17日NBA常规赛进行了10场比赛,20支球队出战,欧文压哨绝杀猛龙勇士吃到3连败76人豪取4连胜凯尔特人爆冷输球湖人逆转掘金。接下来我们一起来看看NBA东部和西部的最新排名CBA最新排名出炉!浙江稳居第1,广东5连胜重返前4上海战绩狂飙CBA常规赛进入到12月中旬之后,对于积分榜格局而言,实际上呈现出比较大的分水岭,本土球员磨合到位,外援状态出色,往往可以在积分榜占据比较高的顺位,像浙江13连胜,吴前,王奕博,刘
少林寺昙宗师傅扮演者于海去世,曾是吴京李连杰的师傅最近噩耗连连,不少名人去世,比如娱乐圈歌唱家谢莉斯,老戏骨管宗祥,绿叶王的陈万雷,以及65岁的知名导演何平,喜剧演员陈佩斯母亲,以及网红许华升的酒神爷爷等等!还有很多网友所述身边的过年家宴聚餐,餐桌上必备的5道硬菜,学会个个夸你是大厨哈喽,这里是清清爱做菜,我是清清!今天17号了,还有3天这样,我们就要开始放假过年,相信这个时候,很多人都已经备好了春节过年里,聚餐的食材了。那春节的家宴聚餐食材都准备好了,那今天微电影牵挂视频加载中春节将至,在地处东北的吉林省白山市松岭村,谢春贵杜惠贤老两口不时向窗外望去,期盼着儿子回家。他们的儿子谢振国,是中国铁路沈阳局集团有限公司梅河口机务段通化运用车间白山机车易点云助力绿色产业发展日前,首届中国生态环保产业服务双碳战略院士论坛在北京召开,中国气候变化事务特使解振华在论坛中指出全球正迎来一场以绿色低碳为特征的产业和技术变革,绿色低碳转型的大趋势不可阻挡,经济技泽连斯基猜对了,普京要让无人机在战场大杀四方,耗尽乌军昂贵导弹无人机在现代战场上的巨大作用,早已在纳卡冲突中得到了证实,如今到了乌克兰战场上,更是成为了明星武器。不仅能执行监视侦察等任务,还可以对敌方关键设施展开有效打击。考虑到无人机在战场上嫁出去的女儿,不能在娘家过年回娘家不能夫妻一起睡,该信吗大年30一个人去住酒店,娘家不是家,这是我最后回来过一个年,以后不会再回来,日后死也不回娘家。最近刷到了上面这篇帖子,这位网友远嫁异地,因为当地有嫁出去的女儿不能在娘家过年的说法,东北锦州话方言中的成语翻车倒辙勾嘎不舍鬼鬼惕惕等头条创作挑战赛翻车倒辙fnchdozh重新装车,改换车辙,意为翻来覆去地变化。例句不是我不成全你们。当初你们要是先让两个孩子结婚,哪能弄得翻车倒辙?让我脱裤子放屁费二遍事!这都怪你哈萨克斯坦政治气氛已变,事关国家未来,托卡耶夫终于动手近一年,中亚五国中最大的国家哈萨克斯坦加强了与中国之间的联系与合作,也让我们比之前更为关注这个国家。近日,哈萨克斯坦一则废除首任总统法的消息,再次引起了各方的关注。我们先来介绍一下彭博社中国多地今年经济增长目标定为5或以上据彭博新闻社网站1月16日报道,中国多数省份都将2023年的经济增长目标定为5或以上。根据彭博社对中国地方政府工作报告的分析,中国省级行政区中的大多数包括经济大省浙江和山东都认为它准妈妈春节指南为了自身和胎儿健康,这些过年传统孕妇不必遵守亲子欢乐过大年文咩小胖春节之所以红红火火热热闹闹,跟它的很多传统习俗是分不开的。但是,也正因为春节的热闹,也让孕妇这一特殊的群体,需要注意一些问题。怀孕以后,无论是准妈妈的身体情况不管生几胎,这种尴尬都无法避免,备孕女性要了解镜子生育是每一个女性都可能要经历的时刻,也是人生中要遭遇的大事,作为女性一定要迈过这道坎,除了依靠现代医学技术的发达,同样也要依靠我们自己对于生育这件事的了解,否则,就算身体没有受