乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      給小師妹展開說說,Spring Bean IOC、AOP 循環(huán)依賴

       小傅哥 2021-12-13


      作者:小傅哥
      博客:https://

      沉淀、分享、成長,讓自己和他人都能有所收獲!😄

      一、前言

      延遲滿足能給你帶來什么?

      大學(xué)有四年時(shí)間,但幾乎所有人都是臨近畢業(yè)才發(fā)現(xiàn)找一份好工作費(fèi)勁,尤其是我能非常熟悉的軟件開發(fā)行業(yè),即使是畢業(yè)了還需要額外花錢到培訓(xùn)機(jī)構(gòu),在學(xué)一遍編程技術(shù)才能出去找工作。好像在校這幾年壓根就沒學(xué)到什么!

      就我個人而言可能是因?yàn)樯蠈W(xué)期間喜歡編程,也從師哥、師姐那里聽到一些關(guān)于畢業(yè)后找工作的不容易,也了解了一些社會上對程序員開發(fā)技能的要求級別。也就是得到了這些消息,又加上自己樂于折騰,我給自己定了一個每天都能完成的小目標(biāo):

      紅塵世界幾個王,我自不服迎頭上。
      日敲代碼兩百行,沖進(jìn)世界五百強(qiáng)。
      

      哈哈哈,就這么每天兩百行代碼,一個月就是6千行,一年就是6萬行,三年后開始實(shí)習(xí)就有18萬行,一個應(yīng)屆實(shí)習(xí)生有將近20萬行代碼的敲擊量,幾乎已經(jīng)可以非常熟練的完成各類簡單的工作,在加上實(shí)習(xí)中對整個項(xiàng)目流程真正的斷鏈后,找一個正經(jīng)的開發(fā)工作,還是很容易的。

      而這時(shí)候找工作的容易,就來自于你一直以來的學(xué)習(xí)和沉淀,但如果你沒經(jīng)過這些努力,可能等畢業(yè)后就會變得非?;艁y,最后沒辦法只能去一些機(jī)構(gòu)再學(xué)習(xí)一遍。

      二、面試題

      謝飛機(jī),小記!,以前感覺Spring沒啥,看過一篇getBean,我的天!

      謝飛機(jī):面試官,最近我看了 Spring 的 getBean 發(fā)現(xiàn)這里好多東西,還有一個是要解決循環(huán)依賴的,這玩意面試有啥要問的嗎?

      面試官:有哇,Spring 是如何解決循環(huán)依賴的?

      謝飛機(jī):嗯,通過三級緩存提前暴露對象解決的。

      面試官:可以哈,那這三個緩存里都存放了什么樣的對象信息呢?

      謝飛機(jī):一級緩存存放的是完整對象,也叫成品對象。二級緩存存放的是半成品對象,就是那些屬性還沒賦值的對象。三級緩存存放的是 ObjectFactory<?> 類型的 lambda 表達(dá)式,就是這用于處理 AOP 循環(huán)依賴的。

      面試官:可以呀,謝飛機(jī)有所準(zhǔn)備嘛!那如果沒有三級緩存,只有二級或者一級,能解決循環(huán)依賴嗎?

      謝飛機(jī):其實(shí)我看過資料了,可以解決,只不過 Spring 要保證幾個事情,只有一級緩存處理流程沒法拆分,復(fù)雜度也會增加,同時(shí)半成品對象可能會有空指針異常。而將半成品與成品對象分開,處理起來也更加優(yōu)雅、簡單、易擴(kuò)展。另外 Spring 的兩大特性中不僅有 IOC 還有 AOP,也就是基于字節(jié)碼增強(qiáng)后的方法,該存放到哪,而三級緩存最主要,要解決的循環(huán)依賴就是對 AOP 的處理,但如果把 AOP 代理對象的創(chuàng)建提前,那么二級緩存也一樣可以解決。但是,這就違背了 Spring 創(chuàng)建對象的原則,Spring 更喜歡把所有的普通 Bean 都初始化完成,在處理代理對象的初始化。

      面試官:飛機(jī),不錯嘛,這次了解了不少。那問個簡單的,你擼過循環(huán)依賴的解決方案?

      謝飛機(jī):哦哦,這沒有,沒實(shí)踐過!!!確實(shí)應(yīng)該搞一下,試試。

      三、什么是循環(huán)依賴?

      1. 問題描述

      了解問題的本質(zhì)再分析問題,往往更利于對問題有更深入的了解和研究。所以我們在分析 Spring 關(guān)于循環(huán)依賴的源碼之前,先要了解下什么是循環(huán)依賴。

      • 循環(huán)依賴分為三種,自身依賴于自身、互相循環(huán)依賴、多組循環(huán)依賴。
      • 但無論循環(huán)依賴的數(shù)量有多少,循環(huán)依賴的本質(zhì)是一樣的。就是你的完整創(chuàng)建依賴于我,而我的完整創(chuàng)建也依賴于你,但我們互相沒法解耦,最終導(dǎo)致依賴創(chuàng)建失敗。
      • 所以 Spring 提供了除了構(gòu)造函數(shù)注入和原型注入外的,setter循環(huán)依賴注入解決方案。那么我們也可以先來嘗試下這樣的依賴,如果是我們自己處理的話該怎么解決。

      2. 問題體現(xiàn)

      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();
      
      }
      
      • 這段代碼就是循環(huán)依賴最初的模樣,你中有我,我中有你,運(yùn)行就報(bào)錯 java.lang.StackOverflowError
      • 這樣的循環(huán)依賴代碼是沒法解決的,當(dāng)你看到 Spring 中提供了 get/set 或者注解,這樣之所以能解決,首先是進(jìn)行了一定的解耦。讓類的創(chuàng)建和屬性的填充分離,先創(chuàng)建出半成品Bean,再處理屬性的填充,完成成品Bean的提供。

      3. 問題處理

      在這部分的代碼中就一個核心目的,我們來自己解決一下循環(huán)依賴,方案如下:

      public class CircleTest {
      
          private final static Map<String, Object> 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> T getBean(Class<T> beanClass) throws Exception {
              String beanName = beanClass.getSimpleName().toLowerCase();
              if (singletonObjects.containsKey(beanName)) {
                  return (T) singletonObjects.get(beanName);
              }
              // 實(shí)例化對象入緩存
              Object obj = beanClass.newInstance();
              singletonObjects.put(beanName, obj);
              // 屬性填充補(bǔ)全對象
              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 兩個類,互相有依賴。但在兩個類中的依賴關(guān)系使用的是 setter 的方式進(jìn)行填充。也就是只有這樣才能避免兩個類在創(chuàng)建之初不非得強(qiáng)依賴于另外一個對象。

      • getBean,是整個解決循環(huán)依賴的核心內(nèi)容,A 創(chuàng)建后填充屬性時(shí)依賴 B,那么就去創(chuàng)建 B,在創(chuàng)建 B 開始填充時(shí)發(fā)現(xiàn)依賴于 A,但此時(shí) A 這個半成品對象已經(jīng)存放在緩存到singletonObjects 中了,所以 B 可以正常創(chuàng)建,在通過遞歸把 A 也創(chuàng)建完整了。

      四、源碼分析

      1. 說說細(xì)節(jié)

      通過上面的例子我們大概了解到,A和B互相依賴時(shí),A創(chuàng)建完后填充屬性B,繼續(xù)創(chuàng)建B,再填充屬性A時(shí)就可以從緩存中獲取了,如下:

      那這個解決事循環(huán)依賴的事放到 Spring 中是什么樣呢?展開細(xì)節(jié)!

      雖然,解決循環(huán)依賴的核心原理一樣,但要放到支撐起整個 Spring 中 IOC、AOP 特性時(shí),就會變得復(fù)雜一些,整個處理 Spring 循環(huán)依賴的過程如下;

      • 以上就是關(guān)于 Spring 中對于一個有循環(huán)依賴的對象獲取過程,也就是你想要的說說細(xì)節(jié)
      • 乍一看是挺多流程,但是這些也基本是你在調(diào)試代碼時(shí)候必須經(jīng)過的代碼片段,拿到這份執(zhí)行流程,再調(diào)試就非常方便了。

      2. 處理過程

      關(guān)于本章節(jié)涉及到的案例源碼分析,已更新到 github:https://github.com/fuzhengwei/interview - interview-31

      以下是單元測試中對AB依賴的獲取Bean操作,重點(diǎn)在于進(jìn)入 getBean 的源碼跟進(jìn);

      @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> T getBean(String name, Class<T> requiredType) throws BeansException {
      return doGetBean(name, requiredType, null, false);
      }
      
      • 從 getBean 進(jìn)入后,獲取 bean 的操作會進(jìn)入到 doGetBean。
      • 之所以這樣包裝一層,是因?yàn)?doGetBean 有很多不同入?yún)⒌闹剌d方法,方便外部操作。

      doGetBean 方法

      protected <T> T doGetBean(
      final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
      throws BeansException {
      
        // 從緩存中獲取 bean 實(shí)例
      Object sharedInstance = getSingleton(beanName);
      
      // mbd.isSingleton() 用于判斷 bean 是否是單例模式
      if (mbd.isSingleton()) {
        // 獲取 bean 實(shí)例
      sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
      @Override
      public Object getObject() throws BeansException {
      try {
        // 創(chuàng)建 bean 實(shí)例,createBean 返回的 bean 實(shí)例化好的
      return createBean(beanName, mbd, args);
      }
      catch (BeansException ex) {
      destroySingleton(beanName);
      throw ex;
      }
      }
      });
      // 后續(xù)的處理操作
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
      }
      
      // ...
      
        // 返回 bean 實(shí)例
      return (T) bean;
      }
      
      • 按照在源碼分析的流程圖中可以看到,這一部分是從 getSingleton 先判斷是否有實(shí)例對象,對于第一次進(jìn)入是肯定沒有對象的,要繼續(xù)往下走。
      • 在判斷 mbd.isSingleton() 單例以后,開始使用基于 ObjectFactory 包裝的方式創(chuàng)建 createBean,進(jìn)入后核心邏輯是開始執(zhí)行 doCreateBean 操作。

      doCreateBean 方法

      protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
      throws BeanCreationException {
      
        // 創(chuàng)建 bean 實(shí)例,并將 bean 實(shí)例包裝到 BeanWrapper 對象中返回
      instanceWrapper = createBeanInstance(beanName, mbd, args);
      
      // 添加 bean 工廠對象到 singletonFactories 緩存中
      addSingletonFactory(beanName, new ObjectFactory<Object>() {
      @Override
      public Object getObject() throws BeansException {
        // 獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執(zhí)行 AOP 相關(guān)邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 bean。
      return getEarlyBeanReference(beanName, mbd, bean);
      }
      });
      
      try {
        // 填充屬性,解析依賴關(guān)系
      populateBean(beanName, mbd, instanceWrapper);
      if (exposedObject != null) {
      exposedObject = initializeBean(beanName, exposedObject, mbd);
      }
      }
      
      // 返回 bean 實(shí)例
      return exposedObject;
      }
      
      • 在 doCreateBean 方法中包括的內(nèi)容較多,但核心主要是創(chuàng)建實(shí)例、加入緩存以及最終進(jìn)行屬性填充,屬性填充就是把一個 bean 的各個屬性字段涉及到的類填充進(jìn)去。
      • createBeanInstance,創(chuàng)建 bean 實(shí)例,并將 bean 實(shí)例包裝到 BeanWrapper 對象中返回
      • addSingletonFactory,添加 bean 工廠對象到 singletonFactories 緩存中
      • getEarlyBeanReference,獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執(zhí)行 AOP 相關(guān)邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 bean。
      • populateBean,填充屬性,解析依賴關(guān)系。也就是從這開始去找尋 A 實(shí)例中屬性 B,緊接著去創(chuàng)建 B 實(shí)例,最后在返回回來。

      getSingleton 三級緩存

      protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 從 singletonObjects 獲取實(shí)例,singletonObjects 是成品 bean
      Object singletonObject = this.singletonObjects.get(beanName);
      // 判斷 beanName ,isSingletonCurrentlyInCreation 對應(yīng)的 bean 是否正在創(chuàng)建中
      if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
        // 從 earlySingletonObjects 中獲取提前曝光未成品的 bean
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 獲取相應(yīng)的 bean 工廠
      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
      if (singletonFactory != null) {
        // 提前曝光 bean 實(shí)例,主要用于解決AOP循環(huán)依賴
      singletonObject = singletonFactory.getObject();
      
      // 將 singletonObject 放入緩存中,并將 singletonFactory 從緩存中移除
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      }
      }
      }
      }
      return (singletonObject != NULL_OBJECT ? singletonObject : null);
      }
      
      • singletonObjects.get(beanName),從 singletonObjects 獲取實(shí)例,singletonObjects 是成品 bean
      • isSingletonCurrentlyInCreation,判斷 beanName ,isSingletonCurrentlyInCreation 對應(yīng)的 bean 是否正在創(chuàng)建中
      • allowEarlyReference,從 earlySingletonObjects 中獲取提前曝光未成品的 bean
      • singletonFactory.getObject(),提前曝光 bean 實(shí)例,主要用于解決AOP循環(huán)依賴

      綜上,是一個處理循環(huán)依賴的代碼流程,這部分提取出來的內(nèi)容主要為核心內(nèi)容,并沒與長篇大論的全部拆取出來,大家在調(diào)試的時(shí)候會涉及的比較多,盡可能要自己根據(jù)流程圖操作調(diào)試幾遍。

      3. 依賴解析

      綜上從我們自己去嘗試解決循環(huán)依賴,學(xué)習(xí)了循環(huán)依賴的核心解決原理。又分析了 Spring 解決的循環(huán)依賴的處理過程以及核心源碼的分析。那么接下來我們在總結(jié)下三級緩存分別不同的處理過程,算是一個總結(jié),也方便大家理解。

      1. 一級緩存能解決嗎?

      • 其實(shí)只有一級緩存并不是不能解決循環(huán)依賴,就像我們自己做的例子一樣。
      • 但是在 Spring 中如果像我們例子里那么處理,就會變得非常麻煩,而且也可能會出現(xiàn) NPE 問題。
      • 所以如圖按照 Spring 中代碼處理的流程,我們?nèi)シ治鲆患壘彺孢@樣存放成品 Bean 的流程中,是不能解決循環(huán)依賴的問題的。因?yàn)?A 的成品創(chuàng)建依賴于 B,B的成品創(chuàng)建又依賴于 A,當(dāng)需要補(bǔ)全B的屬性時(shí) A 還是沒有創(chuàng)建完,所以會出現(xiàn)死循環(huán)。

      2. 二級緩存能解決嗎?

      • 有了二級緩存其實(shí)這個事處理起來就容易了,一個緩存用于存放成品對象,另外一個緩存用于存放半成品對象。
      • A 在創(chuàng)建半成品對象后存放到緩存中,接下來補(bǔ)充 A 對象中依賴 B 的屬性。
      • B 繼續(xù)創(chuàng)建,創(chuàng)建的半成品同樣放到緩存中,在補(bǔ)充對象的 A 屬性時(shí),可以從半成品緩存中獲取,現(xiàn)在 B 就是一個完整對象了,而接下來像是遞歸操作一樣 A 也是一個完整對象了。

      3. 三級緩存解決什么?

      • 有了二級緩存都能解決 Spring 依賴了,怎么要有三級緩存呢。其實(shí)我們在前面分析源碼時(shí)也提到過,三級緩存主要是解決 Spring AOP 的特性。AOP 本身就是對方法的增強(qiáng),是 ObjectFactory<?> 類型的 lambda 表達(dá)式,而 Spring 的原則又不希望將此類類型的 Bean 前置創(chuàng)建,所以要存放到三級緩存中處理。
      • 其實(shí)整體處理過程類似,唯獨(dú)是 B 在填充屬性 A 時(shí),先查詢成品緩存、再查半成品緩存,最后在看看有沒有單例工程類在三級緩存中。最終獲取到以后調(diào)用 getObject 方法返回代理引用或者原始引用。
      • 至此也就解決了 Spring AOP 所帶來的三級緩存問題。本章節(jié)涉及到的 AOP 依賴有源碼例子,可以進(jìn)行調(diào)試

      五、總結(jié)

      • 回顧本文基本以實(shí)際操作的例子開始,引導(dǎo)大家對循環(huán)依賴有一個整體的認(rèn)識,也對它的解決方案可以上手的例子,這樣對后續(xù)的關(guān)于 Spring 對循環(huán)依賴的解決也就不會那么陌生了。
      • 通篇全文下來大家也可以看到,三級緩存并不是非必須不可,只不過在滿足 Spring 自身創(chuàng)建的原則下,是必須的。如果你可以下載 Spring 源碼對這部分代碼進(jìn)行改動下,提前創(chuàng)建 AOP 對象保存到緩存中,那么二級緩存一樣可以解決循環(huán)依賴問題。
      • 關(guān)于循環(huán)依賴可能并不是一個好的編碼方式,如果在自己的程序中還是要盡可能使用更合理的設(shè)計(jì)模式規(guī)避循環(huán)依賴,可能這些方式會增加代碼量,但在維護(hù)上會更加方便。當(dāng)然這不是強(qiáng)制,可以根據(jù)你的需要而來。

      六、系列推薦

      • 你說,怎么把Bean塞到Spring容器?
      • Spring IOC 特性有哪些,不會讀不懂源碼!
      • 關(guān)于 Spring 中 getBean 的全流程源碼解析
      • 久等了,小傅哥的《重學(xué)Java設(shè)計(jì)模式》終于出版了,彩印&紙質(zhì)!
      • 一個Bug,讓我發(fā)現(xiàn)了 Java 界的.AJ(錐)!

        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多