SpringBoot 熱部署神器快速重啟的秘密!
今天咱們來(lái)聊聊這個(gè)熱部署神器?spring-boot-devtools?的運(yùn)行原理,看看它是怎么用這個(gè) ClassLoader ?來(lái)實(shí)現(xiàn)快速重啟,幫我們節(jié)省時(shí)間的!??
文章概要
文章的主旋律如下??

spring.factories
我們直接打開 spring-boot-devtools 源碼 ,找到 spring.factories 文件:

我們一般都本地開發(fā)調(diào)試的,所以就直接看這個(gè) LocalDevToolsAutoConfiguration 類啦??
LocalDevToolsAutoConfiguration
可以看到核心點(diǎn)在 重啟和重載 ??

主角??

我們先來(lái)看看這個(gè) 重啟 中有什么叭??
重啟原理介紹
大概這么一個(gè)思路?? 下面就跟著源碼分析啦??(文末有源碼重啟要點(diǎn)流程圖)

RestartConfiguration
有這么些方法??

從名字上分析,這兩個(gè)方法應(yīng)該是重點(diǎn),邏輯上應(yīng)該是 有一個(gè) watcher 在盯著 classpath ,如果有變動(dòng)的話,就觸發(fā)這個(gè) ClassPathChangedEvent 事件 ??
那么看看這個(gè) watcher 叭 ??
ClassPathFileSystemWatcher

可以看到這里就創(chuàng)建了這個(gè) ClassPathFileSystemWatcher 類 ??

這里我們注意到它實(shí)現(xiàn)了三個(gè)接口,經(jīng)過(guò)前面 Spring 文章的學(xué)習(xí),咱們知道第一步就該看啥了??
根據(jù)類的初始化,先看看有 static 相關(guān)的代碼沒,接著看 構(gòu)造器 ,最后就來(lái)到這個(gè)初始化方法 afterPropertiesSet 啦??
這里沒有 static 方法,構(gòu)造器也很簡(jiǎn)單,就是獲取 FileSystemWatcherFactory ?,ClassPathRestartStrategy 和 監(jiān)視的文件路徑,那么就看看 afterPropertiesSet 寫了什么叭 ??

ClassPathFileChangeListener
這個(gè)也不復(fù)雜,就監(jiān)聽到文件改變后,發(fā)布事件 ClassPathChangedEvent

FileSystemWatcher
接著就是這個(gè) start 方法啦??
很明顯就是開啟一個(gè)線程,那么咱們來(lái)看看線程中到底在 run 什么??

找到這個(gè)任務(wù)類 Watcher ??

可以發(fā)現(xiàn)它的任務(wù)就是一直 scan ,pollInterval 默認(rèn)是 1s ,quietPeriod 默認(rèn)是 0.4s
意思是每次輪詢的時(shí)間是 1s ,包含中間休息的 0.4s ,休息事件是來(lái)確認(rèn)文件在這個(gè)期間沒有再次被改動(dòng)。
改動(dòng)了的話會(huì)回調(diào) FileChangeListener 的 onChange ,對(duì)應(yīng)我們上面的這個(gè) ClassPathFileChangeListener ,會(huì)去發(fā)布事件 ClassPathChangedEvent ??
ApplicationListener
繞了一大圈,終于描述完了這個(gè)監(jiān)視器 ClassPathFileSystemWatcher ,同時(shí),我們也得把目光移到這個(gè)RestartConfiguration 的第二個(gè)核心 監(jiān)聽器 ??
如圖所示,這個(gè)方法的作用就是重啟應(yīng)用 restart

重啟應(yīng)用
重啟的過(guò)程中呢,包括兩個(gè)步驟,第一步 stop ,第二步 start
stop 部分就是毀滅這些東西了,這里也藏了很多細(xì)節(jié),有很多并發(fā)相關(guān)的知識(shí)點(diǎn) ??
比如
一. ReentrantLock ?是寫在 try catch 的里面還是外面?
二. 循環(huán)里的 rootContexts 其實(shí)是 CopyOnWriteArrayList 類型的
三. 通過(guò)強(qiáng)制的 OOM 來(lái)清除所有的 軟/弱引用 (?? 還有這種操作的!)

在 start 的過(guò)程中,是通過(guò)創(chuàng)建這個(gè)重啟線程 RestartLauncher 來(lái)實(shí)現(xiàn)的,可以發(fā)現(xiàn)該類的任務(wù)就是找到 mainclass 并調(diào)用 main 方法,完成重啟。

而在這個(gè)過(guò)程中,就涉及到這個(gè) classloader 啦。
ClassLoader
細(xì)心的小伙伴可以發(fā)現(xiàn)上面這行代碼中,調(diào)用到了這個(gè) ClassLoader ,這個(gè) getContextClassLoader() 是屬于 Thread 類的,通過(guò)它可以獲取到當(dāng)前線程上下文的 ClassLoader 。
Class.forName(this.mainClassName,?false,?getContextClassLoader());
在創(chuàng)建這個(gè) RestartLauncher 線程時(shí),就已經(jīng)將咱們這個(gè) RestartClassLoader ?給傳進(jìn)來(lái)了。


重啟時(shí),就直接通過(guò) RestartClassLoader ?去找到 main 方法,完成重啟。

很明顯這里 破壞了雙親委派機(jī)制,先從自身查找,沒有的話再去父類查找
這里 業(yè)務(wù)代碼 都被 RestartClassLoader 加載了,而每次重啟都會(huì)重新創(chuàng)建這個(gè) RestartClassLoader ?,然后去加載業(yè)務(wù)代碼 ?? (通過(guò)傳進(jìn)來(lái)的 URL ?可以發(fā)現(xiàn))
那么到此,這個(gè) 重啟 的過(guò)程就完成了。
差點(diǎn)忘了,這里還有個(gè)默認(rèn)的監(jiān)視范圍??
監(jiān)視策略
如下圖?? 默認(rèn)策略中,這些路徑下的文件變化不被檢測(cè)

可通過(guò)配置修改
?spring.devtools.restart.exclude=static/**,public/**
總結(jié)
通過(guò)閱讀源碼,我們知道了 spring-boot-devtools 是通過(guò)自定義 RestartClassLoader ?來(lái)加載業(yè)務(wù)代碼,并在重啟時(shí)銷毀它,再重新創(chuàng)建,進(jìn)而重新獲取代碼,實(shí)現(xiàn)這個(gè)快速重啟的。
而其他 jar 包等由另外的 ClassLoader ?加載,不受影響。
同時(shí),也可以看到 Spring 事件機(jī)制 無(wú)處不在的身影,還有各種初始化的操作,以及線程,并發(fā),鎖在重啟過(guò)程中的使用,這些就需要小伙伴們打開源碼自身感受了,如 守護(hù)線程,ReentrantLock ,CopyOnWriteArrayList ,CountDownLatch ,甚至 OOM 都能這么用!
還有 重啟 原來(lái)就是 反射調(diào)用 main 方法 呀??
重啟過(guò)程源碼要點(diǎn)


本文就分享到這里啦,喜歡的朋友點(diǎn)個(gè)贊再走哦

往期推薦

Spring 事務(wù)失效的 8 種場(chǎng)景!

實(shí)戰(zhàn),實(shí)現(xiàn)冪等的8種方案!

絕絕子,畫框架圖就用這個(gè)工具
