<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring解決循環(huán)依賴的思路竟然來自于一道算法題

          共 8839字,需瀏覽 18分鐘

           ·

          2021-06-27 10:29

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          成功路上并不擁擠,因?yàn)閳猿值娜瞬欢唷?/span>

          編輯:業(yè)余草

          juejin.cn/post/6844904122160775176

          推薦:https://www.xttblog.com/?p=5221

          前言

          「Spring」如何解決的循環(huán)依賴,是近兩年流行起來的一道 Java 面試題。我今年也面試過很多自稱“高級”的 Java 工程師,對循環(huán)依賴的回答多數(shù)都不是很理想,今天我們一起來學(xué)習(xí)學(xué)習(xí)它。

          當(dāng)然,我作為面試官,如果你回答不出來循環(huán)以來,我可能會問一些諸如“如果注入的屬性為「null」,你會從哪幾個方向去排查”這些「場景題」等等。

          正文

          通常來說,如果問 Spring 內(nèi)部如何解決循環(huán)依賴,一定是單默認(rèn)的「單例」Bean中,屬性互相引用的場景。

          比如幾個 Bean 之間的互相引用:

          Java Bean 相互引用

          甚至自己“循環(huán)”依賴自己:

          Java Bean 自引用

          先說明前提:「原型」(Prototype)的場景是「不支持」循環(huán)依賴的,通常會走到AbstractBeanFactory類中下面的判斷,拋出異常。

          if (isPrototypeCurrentlyInCreation(beanName)) {  
            throw new BeanCurrentlyInCreationException(beanName);  
          }  

          原因很好理解,創(chuàng)建「新的A」時,發(fā)現(xiàn)要注入「原型字段B」,又創(chuàng)建「新的B」發(fā)現(xiàn)要注入「原型字段A」...

          這就套娃了, 你猜是先「StackOverflow」還是「OutOfMemory」?

          Spring 怕你不好猜,就先拋出了「BeanCurrentlyInCreationException」

          基于構(gòu)造器的循環(huán)依賴,就更不用說了,官方文檔:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-dependency-resolution都攤牌了,你想讓構(gòu)造器注入支持循環(huán)依賴,是不存在的,不如把代碼改了。

          那么默認(rèn)單例的屬性注入場景,「Spring」是如何支持循環(huán)依賴的?

          「Spring」解決循環(huán)依賴

          首先,Spring 內(nèi)部維護(hù)了三個「Map」,也就是我們通常說的「三級緩存」。

          筆者翻閱 Spring 文檔倒是沒有找到三級緩存的概念,可能也是本土為了方便理解的詞匯。

          在 Spring 的DefaultSingletonBeanRegistry類中,你會赫然發(fā)現(xiàn)類上方掛著這三個 Map:

          • singletonObjects 它是我們最熟悉的朋友,俗稱“「單例池」”“「容器」”,緩存創(chuàng)建完成單例 Bean 的地方。

          • singletonFactories 映射創(chuàng)建 Bean 的原始工廠

          • earlySingletonObjects 映射 Bean 的「早期」引用,也就是說在這個 Map 里的 Bean 不是完整的,甚至還不能稱之為“「Bean」”,只是一個「Instance」.

          后兩個 Map 其實(shí)是“「墊腳石」”級別的,只是創(chuàng)建 Bean 的時候,用來借助了一下,創(chuàng)建完成就清掉了。

          所以筆者前文對“三級緩存”這個詞有些迷惑,可能是因?yàn)樽⑨尪际且?Cache of 開頭吧。

          為什么成為后兩個 Map 為「墊腳石」,假設(shè)最終放在「singletonObjects」的 Bean 是你想要的一杯“涼白開”。

          那么 Spring 準(zhǔn)備了兩個杯子,即singletonFactoriesearlySingletonObjects來回“倒騰”幾番,把熱水晾成“涼白開”放到「singletonObjects」中。

          閑話不說,都濃縮在圖里。

          Spring的循環(huán)依賴

          上面的是一張 GIF,如果你沒看到可能還沒加載出來。三秒一幀,不是你電腦卡

          我一共畫了 17 張圖「簡化表述」了 Spring 的主要步驟,GIF 上方即是剛才提到的三級緩存,下方展示是「主要」的幾個方法。

          當(dāng)然了,這個地步你肯定要結(jié)合Spring源碼https://github.com/spring-projects/spring-framework來看,要不肯定看不懂。

          如果你只是想大概了解,或者面試,可以先記住筆者上文提到的“「三級緩存」”,以及下文即將要說的本質(zhì)。

          循環(huán)依賴的本質(zhì)

          上文了解完 Spring 如何處理循環(huán)依賴之后,讓我們跳出“「閱讀源碼」”的思維,假設(shè)讓你實(shí)現(xiàn)一個有以下特點(diǎn)的功能,你會怎么做?

          • 將指定的一些類實(shí)例為單例
          • 類中的字段也都實(shí)例為單例
          • 支持循環(huán)依賴

          舉個例子,假設(shè)有類 A:

          public class A {  
              private B b;  
          }  

          類 B:

          public class B {  
              private A a;  
          }  

          說白了讓你「模仿Spring」「假裝A」「B」是被 @Component 修飾,
          并且類中的字段「假裝」是 @Autowired 修飾的,處理完放到 Map 中。

          其實(shí)非常簡單,筆者寫了一份粗糙的代碼,可供參考:

          /**  
               * 放置創(chuàng)建好的bean Map  
               */
            
              private static Map<String, Object> cacheMap = new HashMap<>(2);

              public static void main(String[] args) {  
                  // 假裝掃描出來的對象  
                  Class[] classes = {A.classB.class};  
                  // 假裝項(xiàng)目初始化實(shí)例化所有bean  
                  for (Class aClass : classes) {  
                      getBean(aClass);  
                  }  
                  // check  
                  System.out.println(getBean(B.class).getA() == getBean(A.class));  
                  System.out.println(getBean(A.class).getB() == getBean(B.class));  
              }

              @SneakyThrows  
              private static <T> getBean(Class<T> beanClass) {  
                  // 本文用類名小寫 簡單代替bean的命名規(guī)則  
                  String beanName = beanClass.getSimpleName().toLowerCase();  
                  // 如果已經(jīng)是一個bean,則直接返回  
                  if (cacheMap.containsKey(beanName)) {  
                      return (T) cacheMap.get(beanName);  
                  }  
                  // 將對象本身實(shí)例化  
                  Object object = beanClass.getDeclaredConstructor().newInstance();  
                  // 放入緩存  
                  cacheMap.put(beanName, object);  
                  // 把所有字段當(dāng)成需要注入的bean,創(chuàng)建并注入到當(dāng)前bean中  
                  Field[] fields = object.getClass().getDeclaredFields();  
                  for (Field field : fields) {  
                      field.setAccessible(true);  
                      // 獲取需要注入字段的class  
                      Class<?> fieldClass = field.getType();  
                      String fieldBeanName = fieldClass.getSimpleName().toLowerCase();  
                      // 如果需要注入的bean,已經(jīng)在緩存Map中,那么把緩存Map中的值注入到該field即可  
                      // 如果緩存沒有 繼續(xù)創(chuàng)建  
                      field.set(object, cacheMap.containsKey(fieldBeanName)  
                              ? cacheMap.get(fieldBeanName) : getBean(fieldClass));  
                  }  
                  // 屬性填充完成,返回  
                  return (T) object;  
              }

          這段代碼的效果,其實(shí)就是處理了循環(huán)依賴,并且處理完成后,cacheMap 中放的就是完整的“「Bean」”了

          循環(huán)依賴

          這就是“「循環(huán)依賴」”的本質(zhì),而不是“Spring如何解決循環(huán)依賴”。

          之所以要舉這個例子,是發(fā)現(xiàn)一小部分盆友陷入了“「閱讀源碼的泥潭」”,而忘記了問題的本質(zhì)。

          為了看源碼而看源碼,結(jié)果一直看不懂,卻忘了本質(zhì)是什么。

          如果真看不懂,不如先寫出基礎(chǔ)版本,逆推 Spring 為什么要這么實(shí)現(xiàn),可能效果會更好。

          what?問題的本質(zhì)居然是two sum!

          看完筆者剛才的代碼有沒有似曾相識?沒錯,和「two sum」的解題是類似的。

          不知道「two sum」是什么梗的,筆者和你介紹一下:

          「two sum」是刷題網(wǎng)站leetcode:https://leetcode-cn.com/problems/two-sum/,序號為 1 的題,也就是大多人的算法入門的第一題。

          常常被人調(diào)侃,有「算法面」的公司,被面試官欽定了,合的來。那就來一道「two sum」走走過場。

          問題內(nèi)容是:給定「一個數(shù)組」,給定「一個數(shù)字」。返回數(shù)組中可以「相加得到指定數(shù)字」的兩個「索引」

          比如:給定nums = [2, 7, 11, 15], target = 9
          那么要返回 [0, 1],因?yàn)?code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">2 + 7 = 9

          這道題的優(yōu)解是,一次遍歷 + HashMap:

          class Solution {  
              public int[] twoSum(int[] nums, int target) {  
                  Map<Integer, Integer> map = new HashMap<>();  
                  for (int i = 0; i < nums.length; i++) {  
                      int complement = target - nums[i];  
                      if (map.containsKey(complement)) {  
                          return new int[] { map.get(complement), i };  
                      }  
                      map.put(nums[i], i);  
                  }  
                  throw new IllegalArgumentException("No two sum solution");  
              }  
          }

          先去 Map 中找「需要的數(shù)字」,沒有就將「當(dāng)前的數(shù)字」保存在 Map 中,如果找到「需要的數(shù)字」,則一起返回。

          和筆者上面的代碼是不是一樣?

          先去緩存里找「Bean」,沒有則「實(shí)例化當(dāng)前的 Bean」放到 Map,如果有需要「依賴」當(dāng)前 Bean 的,就能從 Map 取到。

          結(jié)尾

          如果你是上文筆者提到的“「陷入閱讀源碼的泥潭」”的讀者,上文應(yīng)該可以幫助到你。

          可能還有盆友有疑問,為什么一道“「two-sum」”,Spring 處理的如此復(fù)雜?
          這個想想 Spring 支持多少功能就知道了,各種實(shí)例方式..各種注入方式..各種 Bean 的加載,校驗(yàn)..各種「callback」,aop 處理等等..

          Spring 可不只有「依賴注入」,同樣 Java 也不僅是「Spring」。如果我們陷入了某個“牛角尖”,不妨跳出來看看,可能會更佳清晰哦。

          瀏覽 54
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线免费看操逼视频 | 操操操网站 | 久操久操| 欧美屄视频 | 69精品自拍 |