<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>

          可以,很強,68行代碼實現(xiàn)Bean的異步初始化,粘過去就能用.

          共 24119字,需瀏覽 49分鐘

           ·

          2023-06-07 02:26

          你好呀,我是歪歪。

          前兩天在看 SOFABoot 的時候,看到一個讓我眼前一亮的東西,來給大家盤一下。

          SOFABoot,你可能不眼熟,但是沒關(guān)系,本文也不是給你講這個東西的,你就認(rèn)為它是 SpringBoot 的變種就行了。

          因為有螞蟻金服背書,所以主要是一些金融類的公司在使用這個框架:

          9db92a35652bb3dc00ac58425bbd2a6a.webp

          官方介紹是這樣的:

          SOFABoot 是螞蟻金服開源的基于 Spring Boot 的研發(fā)框架,它在 Spring Boot 的基礎(chǔ)上,提供了諸如 Readiness Check,類隔離,日志空間隔離等能力。在增強了 Spring Boot 的同時,SOFABoot 提供了讓用戶可以在 Spring Boot 中非常方便地使用 SOFA 中間件的能力。

          上面這些功能都很強大,但是我主要是分享一下它的這個小功能:

          https://help.aliyun.com/document_detail/133162.html

          e2a95df2001bc7eef767b8d978788f78.webp

          這個功能可以讓 Bean 的初始化方法在異步線程里面執(zhí)行,從而加快 Spring 上下文加載過程,提高應(yīng)用啟動速度。

          為什么看到功能的時候,我眼前一亮呢,因為我很久之前寫過這篇文章《我是真沒想到,這個面試題居然從11年前就開始討論了,而官方今年才表態(tài)。》

          里面提到的面試題是這樣的:

          Spring 在啟動期間會做類掃描,以單例模式放入 ioc。但是 spring 只是一個個類進(jìn)行處理,如果為了加速,我們?nèi)∠?spring 自帶的類掃描功能,用寫代碼的多線程方式并行進(jìn)行處理,這種方案可行嗎?為什么?

          當(dāng)時通過 issue 找到了官方對于這個問題回復(fù)總結(jié)起來就是:應(yīng)該是先找到啟動慢的根本原因,而不是把問題甩鍋給 Spring。這部分對于 Spring 來說,能不動,就別動。

          084a7160cd8b5a12cc4a3159bfbee27e.webp

          僅從“啟動加速-異步初始化方法”這個標(biāo)題上來看,Spring 官方不支持的東西 SOFABoot 支持了。所以這玩意讓我眼前一亮,我倒要看看你是怎么搞得。

          先說結(jié)論:SOFABoot 的方案能從一定程度上解決問題,但是它依賴于我們編碼的時候指定哪些 Bean 是可以異步初始化的,這樣帶來的好處是不必考慮循環(huán)依賴、依賴注入等等各種復(fù)雜的情況了,壞處就是需要程序員自己去識別哪些類是可以異步初始化的。

          我倒是覺得,程序員本來就應(yīng)該具備“識別自己的項目中哪些類是可以異步初始化”的能力。

          但是,一旦要求程序員來主動去識別了,就已經(jīng)“輸了”,已經(jīng)不夠驚艷了,在實現(xiàn)難度上就不是一個級別的事情了。人家 Spring 想的可是框架給你全部搞定,頂多給你留一個開關(guān),你開箱即用,啥都不用管。

          但是總的來說,作為一次思路演變?yōu)樵创a的學(xué)習(xí)案例來說,還是很不錯的。

          c45cdad566e8484d65a771a10e781520.webp

          我們主要是看實現(xiàn)方案和具體邏輯代碼,以 SOFABoot 為抓手,針對其“異步初始化方法”聚焦下鉆,把源碼當(dāng)做紐帶,協(xié)同 Spring,打出一套“我看到了->我會用了->我拿過來->我看懂了->是我的了->寫進(jìn)簡歷”的組合拳。

          c536b325747c5ba04c30b4dbbadeab2f.webp

          Demo

          先搞個 Demo 出來,演示一波效果,先讓你直觀的看到這是個啥玩意。

          這個 Demo 非常之簡單,幾行代碼就搞定。

          先搞兩個 java 類,里面有一個 init 方法:

          0ce137613c72259a2d73a01d84025ca9.webp

          然后把他們作為 Bean 交給 Spring 管理,Demo 就搭建好了:

          beacac8a5eaf7ff8f895667b7b64575a.webp

          直接啟動項目,啟動時間只需要 1.152s,非常絲滑:

          68c57271a5965ea628114e7508402d31.webp

          然后,注意,我要稍微的變一下形。

          在注入 Bean 的時候觸發(fā)一下初始化方法,模擬實際項目中在 Bean 的初始化階段,既在 Spring 項目啟動過程中,做一些數(shù)據(jù)準(zhǔn)備、配置拉取等相關(guān)操作:

          d5ceaf890d26c22af395bb5f74924a96.webp

          再次重啟一下項目,因為需要執(zhí)行兩個 Bean 的初始化動作,各需要 5s 時間,而且是串行執(zhí)行,所以啟動時間直接來到了 11.188s:

          a1c689d00387cf444a86962542e81cc0.webp

          那么接下來,就是見證奇跡的時刻了。

          我加上 @SofaAsyncInit 這樣的一個注解:

          ad8c9c80899f26dd6e83cbe8887af558.webp

          你先別管這個注解是哪里來的,從這個注解的名稱你也知道它是干啥的:異步執(zhí)行初始化。

          這個時候我再啟動項目:

          9b28b234828a53bc71568605d11a8d62.webp

          從日志中可以看到:

          1. whyBean 和 maxBean 的 init 方法是由兩個不同的線程并行執(zhí)行的。
          2. 啟動時間縮短到了 6.049s。

          所以 @SofaAsyncInit 這個注解實現(xiàn)了“指定 Bean 的初始化方法實現(xiàn)異步化”。

          你想想,如果你有 10 個 Bean,每個 Bean 都需要 1s 的時間做初始化,總計 10s。

          但是這些 Bean 之間其實不需要串行初始化,那么用這個注解,并行只需要 1s,搞定。

          到這里,你算是看到了這樣的東西存在,屬于“我看到了”。

          接下來,我們進(jìn)入到“我會用了”這個環(huán)節(jié)。

          6c5bd3bd0d2c24f889a1d7cd1a917fc8.webp

          怎么來的。

          在解讀原理之前,我還得告訴你這個注解到底是怎么來的。

          它屬于 SOFABoot 框架里面的注解,首先你得把你的 SpringBoot 修改為 SOFABoot。

          這一步參照官方文檔中的“快速開始”部分,非常的簡單:

          https://www.sofastack.tech/projects/sofa-boot/quick-start/

          第一步就是把項目中 pom.xml 中的:

                
                
          ????org.springframework.boot
          ????spring-boot-starter-parent
          ????${spring.boot.version}
          ?????

          替換為:

                
                
          ????com.alipay.sofa
          ????sofaboot-dependencies
          ????${sofa.boot.version}

          這里的 ${sofa.boot.version} 指定具體的 SOFABoot 版本,我這里使用的是最新的 3.18.0 版本。

          然后我們要使用 @SofaAsyncInit 注解,所以需要引入以下 maven:

                
                
          ????com.alipay.sofa
          ????runtime-sofa-boot-starter

          對于 pom.xml 文件的變化,就只有這么一點:

          e5af6fc3b702e8d47aee85bd17e356d4.webp

          最后,在工程的 application.properties 文件下添加 SOFABoot 工程一個必須的參數(shù)配置,spring.application.name,用于標(biāo)示當(dāng)前應(yīng)用的名稱

                
                #?Application?Name
          spring.application.name=SOFABoot?Demo

          就搞定了,我就完成了一個從 SpringBoot 切換為 SOFABoot 這個大動作。

          當(dāng)然了,我這個是一個 Demo 項目,結(jié)構(gòu)和 pom 依賴都非常簡單,所以切換起來也非常容易。如果你的項目比較大的話,可能會遇到一些兼容性的問題。

          但是,注意我要說但是了。

          你是在學(xué)習(xí)摸索階段,Demo 一定要簡單,越小越好,越純凈越好。所以這個切換的動作對你搭建的一個全新的 Demo 項目來說沒啥難度,不會遇到任何問題。

          這個時候,你就可以使用 @SofaAsyncInit 注解了:

          d5852fcc40820167234ce0a0a68e555d.webp

          到這里,恭喜你,會用了。

          拿來吧你

          不知道你看到這里是什么感受。

          反正對于我來說,如果僅僅是為了讓我能使用這個注解,達(dá)到異步初始化的目的,要讓我從熟悉的 SpringBoot 修改為聽都沒聽過的 SOFABoot,即使這個框架背后有阿里給它背書,我肯定也是不會這么干的。

          所以,對于這一類“人有我無”的東西,我都是采取“拿來吧你”策略。

          2dfd10b89176fc168e453a29da03b867.webp

          你想,最開始的我就說了,SOFABoot 是 SpringBoot 的變種,它的底層還是 SpringBoot。

          而 SOFABoot 又是開源的,整個項目的源碼我都有了:

          https://github.com/sofastack/sofa-boot/blob/master/README_ZH.md

          從其中剝離出一個基于 SpringBoot 做的小功能,融入到我自己的 SpringBoot 項目中,還玩意難道不是手到擒來的事情?

          不過就是稍微高級一點的 cv 罷了。

          首先,你得把 SOFABoot 的源碼下載下來,或者在另外的一個項目中引用它,把自己的項目恢復(fù)為一個 SpringBoot 項目。

          72ce093f30fd8f742d40206c6b50fe03.webp

          我這邊是直接把 SOFABoot 源碼搞下來了,先把源碼里面的 @SofaAsyncInit 注解粘到項目里面來,然后從 @SofaAsyncInit 注解入手,發(fā)現(xiàn)除了測試類只有一個 AsyncInitBeanFactoryPostProcessor 類在對其進(jìn)行使用:

          a807f80ae6b5bf518efda024aa245f3b.webp

          所以把這個類也搬運過來。

          搬運過來之后你會發(fā)現(xiàn)有一些類找不到導(dǎo)致報錯:

          40a772f4aa84af4a13ce6ac304e702b6.webp

          針對這部分類,你可以采取無腦搬運的方式,也可以稍加思考替換一些。

          比如我就分為了兩種類型:

          6c5563d64520376eab3975fd08a73606.webp

          標(biāo)號為 ① 的部分,我是直接粘貼到自己的項目中,然后使用項目中的類。

          標(biāo)號為 ② 的部分,比如 BeanLoadCostBeanFactory 和 SofaBootConstants,他們的目的是為了獲取一個 moduleName 變量:

          241b6a7f768422fc908b187617253b74.webp

          我也不知道這個 moduleName 是啥,所以我采取的策略是自己指定一個:

          5604c7a4826f7e5454e7e27d08358152.webp

          至于 ErrorCode 和 SofaLogger,日志相關(guān)的,就用自己項目里面的日志就行了。

          就是這個意思:

          602c89f47e1ab10cd60864a3287e555d.webp

          這樣處理完成之后,AsyncInitBeanFactoryPostProcessor 類不報錯了,接著看這個類在哪里使用到了。

          就這樣順藤摸瓜,最后搬運完成之后,就是這些類移過來了:

          d3be8cfbdaa2db393ddb7bcf96b99f09.webp

          除了這些類之外,你還會把這個 spring.factories 搬運過來,在項目啟動時把這幾個相關(guān)的類加載進(jìn)去:

          421269c30966da9b32e4d83e8ced1e50.webp

          然后再次啟動這個和 SOFABoot 沒有一點關(guān)系的項目:

          926289ecb3bf1dffcb2cd316283a25df.webp

          你會發(fā)現(xiàn),你的項目也具備異步初始化 Bean 的功能了。

          你要再進(jìn)一步,把它直接封裝為一個 spring-boot-starter-asyncinitbean,發(fā)布到你們公司的私服里面。

          其他團隊也能開箱即用的使用這個功能了。

          別問,問就是你自己獨立開發(fā)出來的,掌握全部源碼,技術(shù)風(fēng)險可控:

          4a38afef70f0b8597130c36b7d27fbf9.webp

          啃原理

          在開始啃原理之前,我先多比比兩句。

          我寫文章的時候,為什么要把“拿來吧你”這一小節(jié)放在“啃原理”之前,我是有考慮的。

          當(dāng)我們把“異步初始化”這個功能點剝離出來之后,你會發(fā)現(xiàn),要實現(xiàn)這個功能,一共也沒涉及到幾個類。

          聚焦點從一整個項目變成了幾個類而已,至少從感官上不會覺得那么的難,對閱讀其源碼產(chǎn)生太大的抗拒心理。

          而我之前很多關(guān)于源碼閱讀的文章,都強調(diào)過這一點:帶著疑問去調(diào)試源碼,要抓住主干,謹(jǐn)防走偏。

          前面這一小節(jié),不過是把這一句話具化了而已。即使沒有把這些類剝離出來,你直接基于 SOFABoot 來調(diào)試這個功能。在你搞清楚“異步初始化”這個功能的實現(xiàn)原理之前,理論上你的關(guān)注點和注意力不應(yīng)該被上面這些類之外的任何一個類給吸引走。

          bb4d5c9d183cb9a80c0c54d8be3599dc.webp

          接下來,我們就帶你啃一下原理。

          關(guān)于原理部分,我們的突破口肯定是看 @SofaAsyncInit 這個注解的在哪個地方被解析的。

          你仔細(xì)看這個注解里面有一個 value 屬性,默認(rèn)為 true,上面的注解說:用來標(biāo)注是否應(yīng)該對 init 方法進(jìn)行異步調(diào)用。

          a291a196258ca3533ad94422488bc093.webp

          而使用到這個 value 值的地方,就只有下面這一個地方:

          com.alipay.sofa.runtime.spring.AsyncInitBeanFactoryPostProcessor#registerAsyncInitBean

          81243f9dd94519b4ac5ff8687358e87d.webp87c6a0d0e7f65431cf8929458e8125b0.webp

          判斷為 true 的時候,執(zhí)行了一個 registerAsyncInitBean 方法。

          從方法名稱也知道,它是把可以異步執(zhí)行的 init 方法的 Bean 收集起來。

          所以看源碼可以看出,這里面是用 Map 來進(jìn)行的存儲,提供了一個 register 和 get 方法:

          381e440eb6580286a14a9e8dc6ead375.webp

          那么這個 Map 里面到底放的是啥呢?

          我也不知道,打個斷點瞅一眼,不就行了:

          46e001fb04dcd009482c6c702b92be87.webp

          通過斷點調(diào)試,我們知道這個里面把項目中哪些 Bean 可以異步執(zhí)行 init 方法通過 Map 存放了起來。

          那么問題就來了:它怎么知道哪些 Bean 可以異步執(zhí)行 init 呢?

          很簡單啊,因為我在對應(yīng)的 Bean 上打上了 @SofaAsyncInit 注解。所以可以通過掃描注解的方式找到這些 Bean。

          所以你說 AsyncInitBeanFactoryPostProcessor 這個類是在干啥?

          肯定核心邏輯就是在解析標(biāo)注了 @SofaAsyncInit 注解的地方嘛。

          4e5ca41bc73fac02abc4641cef1cf470.webp

          到這里,我們通過注解的 value 屬性,找到了 AsyncInitBeanHolder 這個關(guān)鍵類。

          知道了這個類里面有一個 Map,里面維護(hù)的是所有可以異步執(zhí)行 init 方法的 Bean 和其對應(yīng)的 init 方法。

          好,你思考一下,接下來應(yīng)該干啥?

          接下來肯定是看哪個地方在從這個 Map 里面獲取數(shù)據(jù)出來,獲取數(shù)據(jù)的時候,就說明是要異步執(zhí)行這個 Bean 的 init 方法的時候。

          不然它把數(shù)據(jù)放到 Map 里面干啥?玩嗎?

          調(diào)用 getAsyncInitMethodName 方法的地方,也在 AsyncProxyBeanPostProcessor 類里面:

          com.alipay.sofa.runtime.spring.AsyncProxyBeanPostProcessor#postProcessBeforeInitialization

          9dcebcf661e5759a654544fedfeafc4e.webp

          AsyncProxyBeanPostProcessor 類實現(xiàn)了 BeanPostProcessor 接口,并重新了其 postProcessBeforeInitialization 方法。

          在這個 postProcessBeforeInitialization 方法里面,執(zhí)行了從 Map 里面拿對象的動作。

          如果獲取到了則通過 AOP 編程,編織進(jìn)一個 AsyncInitializeBeanMethodInvoker 方法。

          把 bean, beanName, methodName 都傳遞了進(jìn)去:

          f049d5507526ba333158b7a06a852cff.webp

          所以關(guān)鍵點,就在 AsyncInitializeBeanMethodInvoker 里面,因為這個里面有真正判斷是否要進(jìn)行異步初始化的邏輯,主要解讀一下這個類。

          首先,關(guān)注一下它的這三個參數(shù):

          7e65a3ea305e37e82ef514e12c569c8a.webp
          • initCountDownLatch:是 CountDownLatch 對象,其中 count 初始化為 1
          • isAsyncCalling:表示是否正在異步執(zhí)行 init 方法。
          • isAsyncCalled:表示是否已經(jīng)異步執(zhí)行過 init 方法。

          通過這三個字段,就可以感知到一個 Bean 是否已經(jīng)或者正在異步執(zhí)行其 init 方法。

          這個類的核心邏輯就是把可以異步執(zhí)行、但是還沒有執(zhí)行 init 方法的 bean ,把它的 init 方法扔到線程池里面去執(zhí)行:

          0c4092b3d1f126107ee8ef067f3d6f43.webp

          看一下在上面的 invoke 方法中的 if 方法:

          if (!isAsyncCalled && methodName.equals(asyncMethodName))

          isAsyncCalled,首先判斷是否已經(jīng)異步執(zhí)行過這個 bean 的 init 方法了。

          然后看看 methodName.equals(asyncMethodName),要反射調(diào)用的方法是否是之前在 map 中維護(hù)的 init 方法。

          如果都滿足,就扔到線程池里面去執(zhí)行,這樣就算是完成了異步 init。

          如果不滿足呢?

          首先,你想想不滿足的時候說明什么情況?

          是不是說明一個 Bean 的 init 方法在項目啟動過程中不只被調(diào)用一次。

          就像是這樣:

          fd47ecfbd98b7b15caddbf17f63e5da2.webp

          雖然,我不知道為什么一個 Bean 要執(zhí)行兩次 init 方法,大概率是代碼寫的有問題。

          但是我不說,我也不給你拋出異常,我反正就是給你兼容了。

          所以,這段代碼就是在處理這個情況:

          9ce3704d37c63f18ea4cf39384da0d57.webp

          如果發(fā)現(xiàn)有多次調(diào)用,那么只要第一次異步初始化完成了,即 isAsyncCalling 為 false ,你可以繼續(xù)執(zhí)行反射調(diào)用初始化方法的動作。

          這個 invoke 方法的邏輯就是這樣,主要是有一個線程池在里面。

          那么這個線程池是哪里來的呢?

          com.alipay.sofa.runtime.spring.async.AsyncTaskExecutor

          7a24620c0bafbf5cb020146ae7849934.webp

          在第一次 submit 任務(wù)的時候,框架會幫我們初始化一個線程池出來。

          然后通過這個線程池幫我們完成異步初始化的目標(biāo)。

          所以你想想,整個過程是非常清晰的。首先找出來哪些 Bean 上標(biāo)注了 @SofaAsyncInit 注解,找個 Map 維護(hù)起來,接著搞個 AOP 切面,看看哪些 Bean 能在 Map 里面找到,在線程池里面通過動態(tài)代理,調(diào)用其 init 方法。

          就完了。

          對不對?

          好,那么問題就來了?

          為什么我不直接在 init 方法里面搞個線程池呢,就像是這樣。

          先注入一個自定義線程池,同時注釋掉 @SofaAsyncInit 注解:

          c0c144b5fe37c368392975c492bda5c2.webp

          在指定 Bean 的 init 方法中使用該線程池:

          807f9d845fb3607fde0721bfe185337b.webp

          這也不也是能達(dá)到“異步初始化”的目的嗎?

          你說對不對?

          a80ffc372b775d456fe8a6f2416fac45.webp

          不對啊,對個錘子對。

          你看啟動日志:

          a1c24cba75d45bafd1a8d3350a1d1770.webp

          服務(wù)已經(jīng)啟動完成了,但是 4s 之后,Bean 的 init 方法才執(zhí)行完畢。

          在這期間,如果有請求要使用對應(yīng)的 Bean 怎么辦?

          拿著一個還未執(zhí)行完成 init 方法的 Bean 框框一頓用,這畫面想想就很美。

          所以怎么辦?

          我也不知道,看一下 SOFABoot 里面是怎么解決這個問題的。

          在我們前面提到的線程池里面,有這樣的一個方法:

          com.example.asyncthreadpool.spring.AsyncTaskExecutor#ensureAsyncTasksFinish

          f7660c02ca4569772cb5225c55ad2ad2.webp

          在這個方法里面,調(diào)用了 future 的 get 方法進(jìn)行阻塞等待。當(dāng)所有的 future 執(zhí)行完成之后,會關(guān)閉線程池。

          這個 FUTURES 是什么玩意,怎么來的?

          它就是執(zhí)行 submitTask 方法時,維護(hù)進(jìn)行去的,里面裝的就是一個個異步執(zhí)行的 init 方法:

          f2372a6fb580d7265b515fbd8a428914.webp

          所以它通過這個方法可以確保能感知到所有的通過這個線程池執(zhí)行的 init 方法都執(zhí)行完畢。

          現(xiàn)在,方法有了,你先思考一下,我們什么時候觸發(fā)這個方法的調(diào)用呢?

          是不是應(yīng)該在 Spring 容器告訴你:小老弟,我這邊所有的 Bean 都搞定了,你這邊啥情況了?

          這個時候你就需要調(diào)用一下這個方法。

          而 Spring 容器加載完成之后,會發(fā)布這樣的一個事件。也就是它:

          eb17ee9a79c567441ca87cd824cd9dc7.webp

          所以,SOFABoot 的做法就是監(jiān)聽這個事件:

          com.example.asyncthreadpool.spring.AsyncTaskExecutionListener

          ad33d9cf798d2ae8231b6c48876fc801.webp

          這樣,即可確保在異步線程中執(zhí)行的 init 方法的 Bean 執(zhí)行完成之后,容器才算啟動成功,對外提供服務(wù)。

          到這里,原理部分我算是講完了。

          但是寫到這里的時候,我突然冒出了一個寫之前沒有過的想法:在整個實現(xiàn)的過程中,最關(guān)鍵的有兩個東西:

          1. 一個 Map:里面維護(hù)的是所有可以異步執(zhí)行 init 方法的 Bean 和其對應(yīng)的 init 方法。
          2. 一個線程池:異步執(zhí)行 init 方法。

          而這個 Map 是怎么來的?

          不是通過掃描 @SofaAsyncInit 注解得到的嗎?

          那么掃描出來的 @SofaAsyncInit 怎么來的?

          不就是我寫代碼的時候主動標(biāo)注上去的嗎?

          所以,我們是不是可以完全不用 Map ,直接使用異步線程池:

          246eff606fad1c60cc9a98db749f6773.webp

          剩去中間環(huán)節(jié),直接一步到位,只需要留下兩個類即可:

          b7b85c4e9ffa12f0b27ca67c5a864305.webp

          我這里把這個兩個類貼出來。

          AsyncTaskExecutionListener:

                
                public?class?AsyncTaskExecutionListener?implements?PriorityOrdered,
          ???????????????????????????????????????ApplicationListener,
          ???????????????????????????????????????ApplicationContextAware?{
          ????private?ApplicationContext?applicationContext;

          ????@Override
          ????public?void?onApplicationEvent(ContextRefreshedEvent?event)?{
          ????????if?(applicationContext.equals(event.getApplicationContext()))?{
          ????????????AsyncTaskExecutor.ensureAsyncTasksFinish();
          ????????}
          ????}

          ????@Override
          ????public?int?getOrder()?{
          ????????return?Ordered.HIGHEST_PRECEDENCE?+?1;
          ????}

          ????@Override
          ????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
          ????????this.applicationContext?=?applicationContext;
          ????}
          }

          AsyncTaskExecutor:

                
                @Slf4j
          public?class?AsyncTaskExecutor?{
          ????protected?static?final?int?CPU_COUNT?=?Runtime.getRuntime().availableProcessors();
          ????protected?static?final?AtomicReference?THREAD_POOL_REF?=?new?AtomicReference();

          ????protected?static?final?List?FUTURES?=?new?ArrayList<>();

          ????public?static?Future?submitTask(Runnable?runnable)?{
          ????????if?(THREAD_POOL_REF.get()?==?null)?{
          ????????????ThreadPoolExecutor?threadPoolExecutor?=?createThreadPoolExecutor();
          ????????????boolean?success?=?THREAD_POOL_REF.compareAndSet(null,?threadPoolExecutor);
          ????????????if?(!success)?{
          ????????????????threadPoolExecutor.shutdown();
          ????????????}
          ????????}
          ????????Future?future?=?THREAD_POOL_REF.get().submit(runnable);
          ????????FUTURES.add(future);
          ????????return?future;
          ????}

          ????private?static?ThreadPoolExecutor?createThreadPoolExecutor()?{
          ????????int?threadPoolCoreSize?=?CPU_COUNT?+?1;
          ????????int?threadPoolMaxSize?=?CPU_COUNT?+?1;
          ????????log.info(String.format(
          ????????????????"create?why-async-init-bean?thread?pool,?corePoolSize:?%d,?maxPoolSize:?%d.",
          ????????????????threadPoolCoreSize,?threadPoolMaxSize));
          ????????return?new?ThreadPoolExecutor(threadPoolCoreSize,?threadPoolMaxSize,?30,
          ????????????????TimeUnit.SECONDS,?new?LinkedBlockingQueue<>(),?new?ThreadPoolExecutor.CallerRunsPolicy());
          ????}

          ????public?static?void?ensureAsyncTasksFinish()?{
          ????????for?(Future?future?:?FUTURES)?{
          ????????????try?{
          ????????????????future.get();
          ????????????}?catch?(Throwable?e)?{
          ????????????????throw?new?RuntimeException(e);
          ????????????}
          ????????}

          ????????FUTURES.clear();
          ????????if?(THREAD_POOL_REF.get()?!=?null)?{
          ????????????THREAD_POOL_REF.get().shutdown();
          ????????????THREAD_POOL_REF.set(null);
          ????????}
          ????}
          }

          你只需要把這兩個類,一共 68 行代碼,粘到你的項目中,然后把 AsyncTaskExecutionListener 以 @Bean 的方式注入:

                
                @Bean
          public?AsyncTaskExecutionListener?asyncTaskExecutionListener()?{
          ????return?new?AsyncTaskExecutionListener();
          }

          恭喜你,你項目中的 Bean 也可以異步執(zhí)行 init 方法了,使用方法就像這樣式兒的:

          e05a52b892014135917a53d39e6c4616.webp

          但是,如果你要對比這兩種寫的法的話:

          b806971fddac2721876839e72880b7a7.webp

          肯定是選注解嘛,優(yōu)雅的一比。

          所以,我現(xiàn)在問你一個問題:清理聊聊異步初始化 Bean 的思路。

          然后在追問你一個問題:如果通過自定義注解的方式實現(xiàn)?需要用到 Spring 的那些擴展點?

          dc0c1d5deb841176f7866d54fa34f0af.webp還思考個毛啊,不就是這個過程嗎?

          0fd34bab605aedaf32d7d6ce719ef33b.webp

          回想一下前面的內(nèi)容,是不是品出點味道了,是不是有點感覺了,是不是覺得自己又行了?

          其實說真的,這個方案,當(dāng)需要人來主動標(biāo)識哪些 Bean 是可以異步初始化的時候,就已經(jīng)“輸了”,已經(jīng)不夠驚艷了。

          但是,你想想本文只是想教你“異步初始化”這個點嗎?

          不是的,只是以“異步初始化”為抓手,試圖教你一種源碼解讀的方法,找到撕開 Spring 框架的又一個口子,這才是重要的。

          最后,前兩天阿里開發(fā)者公眾號也發(fā)布了一篇叫《Bean異步初始化,讓你的應(yīng)用啟動飛起來》的文章,想要達(dá)成的目的一樣,但是最終的落地方案可以說差距很大。這篇文章沒有具體的源碼,但是也可以對比著看一下,取長補短,融會貫通。

          行了,我就帶你走到這了,我只是給你指個路,剩下的路就要你自己走了。

          天黑路滑,燈火昏暗,抓住主干,及時回頭。

          好了,本文的技術(shù)部分就到這里啦。

          下面這個環(huán)節(jié)叫做[荒腔走板],技術(shù)文章后面我偶爾會記錄、分享點生活相關(guān)的事情,和技術(shù)毫無關(guān)系。我知道看起來很突兀,但是我喜歡,因為這是一個普通博主的生活氣息。

          荒腔走板

          2e05edca8bae6720c6bafa8fe7088cd2.webp

          小區(qū)樓下有一個房地產(chǎn)中介,前段時間每個工作日早上 9 點左右就會在樓下喊口號,整齊劃一,聲音洪亮。

          他們確實在一定程度上給我提供了“叫醒服務(wù)”。

          當(dāng)然,我不是想說他們擾民,因為我也需要這樣的服務(wù)。

          我每天早上的鬧鐘是 8 點 30 分和 8 點 45 分,之前一般來說我在 9 點之前肯定會起床。但是有了這一項叫醒服務(wù)之后,我有幾次懶床到了 9 點。

          上周四的時候,我一覺睡醒發(fā)現(xiàn)已經(jīng)是 9 點 40 分了,嚇得我一蹦就起床了,上班這么多年,我每次雖然都是精準(zhǔn)卡點,但是也算是從不遲到,我已經(jīng)想不起上次遲到是什么時候了。還給 Max 同學(xué)抱怨說今天的“叫醒服務(wù)”我很不滿意。

          趕著去上班的路上我就覺得搞笑:我竟然依賴這虛無縹緲的“叫醒服務(wù)”。

          然后又覺得狼狽,上班遲到之后心里上的狼狽。

          繼而又給自己畫了個餅:以后找個上班不用打卡的公司,最好是事業(yè)單位。

          想著想著到公司樓下了,掏出手機打卡,發(fā)現(xiàn)并未顯示遲到。

          才發(fā)現(xiàn):哦,原來我昨天晚上加班了,走的晚,本來今天就是可以晚到的。

          害,早知道再睡十分鐘了。

          · · · · · · · · · · · · · · ? ? E N D ? ? · · · · · · · · · · · · · ·

          37e603f0f2ed3c54aefbb673d9ec3251.webp

          推薦?? 這是一行牛逼的源碼。

          ?? @ E v e n t L i s t e n e r

          ??

          ?? 聯(lián) 網(wǎng)

          ?? 發(fā)

          進(jìn) 創(chuàng) 業(yè) 術(shù) t i t l e

          當(dāng) 調(diào) 業(yè) 進(jìn)

          藍(lán)

          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美少妇18p | 一区二区三区四区视频精品免费 | 青娱乐精品在线观看视频 | 波多野结衣一级婬片A片免费下载 | 蜜桃91精品秘 成人取精库 |