SpringBoot 热部署神器快速重启的秘密!
共 2707字,需浏览 6分钟
·
2022-01-11 16:08
今天咱们来聊聊这个热部署神器 spring-boot-devtools 的运行原理,看看它是怎么用这个 ClassLoader 来实现快速重启,帮我们节省时间的!😝
文章概要
文章的主旋律如下👇
![](https://filescdn.proginn.com/51f7211e07a15c6da59325f86de5941d/261450d5dd1e938ec3921b79aa71941a.webp)
spring.factories
我们直接打开 spring-boot-devtools 源码 ,找到 spring.factories 文件:
![](https://filescdn.proginn.com/d21c614c499646b72cc193c356954776/d0c3643b2d69630065bd017d1e67ab21.webp)
我们一般都本地开发调试的,所以就直接看这个 LocalDevToolsAutoConfiguration 类啦😋
LocalDevToolsAutoConfiguration
可以看到核心点在 重启和重载 👇
![](https://filescdn.proginn.com/bc059bdbf61f02ecf818bbbef3493ada/c28d3e06de568e32cc838d4f72d1dcab.webp)
主角👇
![](https://filescdn.proginn.com/aceff1c831f532dc5b7ef28a296fd979/d8178268404d95ba2ab9a2351bb9d67a.webp)
我们先来看看这个 重启 中有什么叭😄
重启原理介绍
大概这么一个思路👇 下面就跟着源码分析啦😄(文末有源码重启要点流程图)
![](https://filescdn.proginn.com/a78361653f6fb71d51e4ae135b1b82b8/683f8824968098f70ad2265830e5abfd.webp)
RestartConfiguration
有这么些方法👇
![](https://filescdn.proginn.com/0b58076536c7846933898e57a251c223/ccb981a2dfd4bbff0248b1f4d7986e35.webp)
从名字上分析,这两个方法应该是重点,逻辑上应该是 有一个 watcher 在盯着 classpath ,如果有变动的话,就触发这个 ClassPathChangedEvent 事件 😝
那么看看这个 watcher 叭 😄
ClassPathFileSystemWatcher
![](https://filescdn.proginn.com/7e2d918f6dc6b773567c171f4323f258/bc3a3934ef19ac9557808b740b0ac562.webp)
可以看到这里就创建了这个 ClassPathFileSystemWatcher 类 👇
![](https://filescdn.proginn.com/a3784357e8e7705a92d7833c949366df/47f771324e1b8a4598712685fd759a14.webp)
这里我们注意到它实现了三个接口,经过前面 Spring 文章的学习,咱们知道第一步就该看啥了😄
根据类的初始化,先看看有 static 相关的代码没,接着看 构造器 ,最后就来到这个初始化方法 afterPropertiesSet 啦😄
这里没有 static 方法,构造器也很简单,就是获取 FileSystemWatcherFactory ,ClassPathRestartStrategy 和 监视的文件路径,那么就看看 afterPropertiesSet 写了什么叭 👇
![](https://filescdn.proginn.com/732dac96c3c2d5688012b3345e59f4e4/aafb39ddd4940d52d1f551c67b85079d.webp)
ClassPathFileChangeListener
这个也不复杂,就监听到文件改变后,发布事件 ClassPathChangedEvent
![](https://filescdn.proginn.com/569afbe4cbb36533f6bfc433d91331a6/a38bb2350118da11a2df334dfbd4c6bd.webp)
FileSystemWatcher
接着就是这个 start 方法啦👇
很明显就是开启一个线程,那么咱们来看看线程中到底在 run 什么🐷
![](https://filescdn.proginn.com/08952656a158f4a72c01ec634704c219/4350e9db56a7eded1610f4fac6c319dd.webp)
找到这个任务类 Watcher 👇
![](https://filescdn.proginn.com/24760fa6bd7957dd329a237da09f9690/2628b4cef365963eb3b45dfa478fac23.webp)
可以发现它的任务就是一直 scan ,pollInterval 默认是 1s ,quietPeriod 默认是 0.4s
意思是每次轮询的时间是 1s ,包含中间休息的 0.4s ,休息事件是来确认文件在这个期间没有再次被改动。
改动了的话会回调 FileChangeListener 的 onChange ,对应我们上面的这个 ClassPathFileChangeListener ,会去发布事件 ClassPathChangedEvent 😋
ApplicationListener
绕了一大圈,终于描述完了这个监视器 ClassPathFileSystemWatcher ,同时,我们也得把目光移到这个RestartConfiguration 的第二个核心 监听器 👇
如图所示,这个方法的作用就是重启应用 restart
![](https://filescdn.proginn.com/39a94a44c6e7ef837f62cb6f8496afe0/8cf63b33b31b08e3e46d878f6ae0e39d.webp)
重启应用
重启的过程中呢,包括两个步骤,第一步 stop ,第二步 start
stop 部分就是毁灭这些东西了,这里也藏了很多细节,有很多并发相关的知识点 😋
比如
一. ReentrantLock 是写在 try catch 的里面还是外面?
二. 循环里的 rootContexts 其实是 CopyOnWriteArrayList 类型的
三. 通过强制的 OOM 来清除所有的 软/弱引用 (😱 还有这种操作的!)
![](https://filescdn.proginn.com/655df9ee630c72191fffee3f34d15ada/14ac406430879fa07cf6dbb34de88adb.webp)
在 start 的过程中,是通过创建这个重启线程 RestartLauncher 来实现的,可以发现该类的任务就是找到 mainclass 并调用 main 方法,完成重启。
![](https://filescdn.proginn.com/7096ea01cc9a96197de740dd52ded67d/e4244a86cd37af1ab532d10a418344b8.webp)
而在这个过程中,就涉及到这个 classloader 啦。
ClassLoader
细心的小伙伴可以发现上面这行代码中,调用到了这个 ClassLoader ,这个 getContextClassLoader() 是属于 Thread 类的,通过它可以获取到当前线程上下文的 ClassLoader 。
Class.forName(this.mainClassName, false, getContextClassLoader());
在创建这个 RestartLauncher 线程时,就已经将咱们这个 RestartClassLoader 给传进来了。
![](https://filescdn.proginn.com/acae8c538e1c9a719f7a6f7114c6ed21/875a5a7fd2886bbb0ba95543b0671850.webp)
![](https://filescdn.proginn.com/ed15b44c5eb0e2322a85e1c730f5b182/39bd35b1aed61028477b02408cf35cac.webp)
重启时,就直接通过 RestartClassLoader 去找到 main 方法,完成重启。
![](https://filescdn.proginn.com/ef7550f3bdb13e3555e4367c207a9b9c/1fa933e092846692563b42241b7d158f.webp)
很明显这里 破坏了双亲委派机制,先从自身查找,没有的话再去父类查找
这里 业务代码 都被 RestartClassLoader 加载了,而每次重启都会重新创建这个 RestartClassLoader ,然后去加载业务代码 🐖 (通过传进来的 URL 可以发现)
那么到此,这个 重启 的过程就完成了。
差点忘了,这里还有个默认的监视范围🐷
监视策略
如下图👇 默认策略中,这些路径下的文件变化不被检测
![](https://filescdn.proginn.com/d714f612689f9b617bcb796ad68ec394/f642fa8fb19601484890693886ba86eb.webp)
可通过配置修改
spring.devtools.restart.exclude=static/**,public/**
总结
通过阅读源码,我们知道了 spring-boot-devtools 是通过自定义 RestartClassLoader 来加载业务代码,并在重启时销毁它,再重新创建,进而重新获取代码,实现这个快速重启的。
而其他 jar 包等由另外的 ClassLoader 加载,不受影响。
同时,也可以看到 Spring 事件机制 无处不在的身影,还有各种初始化的操作,以及线程,并发,锁在重启过程中的使用,这些就需要小伙伴们打开源码自身感受了,如 守护线程,ReentrantLock ,CopyOnWriteArrayList ,CountDownLatch ,甚至 OOM 都能这么用!
还有 重启 原来就是 反射调用 main 方法 呀🐷
重启过程源码要点
![](https://filescdn.proginn.com/7a739a9ead68213893a0317ad15f1194/b9eeb7c311bf5004e8ae5659e58ef9b3.webp)
![](https://filescdn.proginn.com/eb025d35290f361f73a1169c05a4ece9/8ad39c1b6515512f2db95dee886a73c4.webp)
本文就分享到这里啦,喜欢的朋友点个赞再走哦
![](https://filescdn.proginn.com/99fa61e1cecabec08601e2c7d1bc3f61/4e13319ff4f6be4033c79b03f625428b.webp)
往期推荐
![](https://filescdn.proginn.com/f2273037c4c7c7d3ba11855c5e7d5474/5cef014c350f80e8e2d107c2c290bc1a.webp)
Spring 事务失效的 8 种场景!
![](https://filescdn.proginn.com/29b989ee113c64b7381353024f2b3559/d64960537729c3cf4745f848d27dbf9d.webp)
实战,实现幂等的8种方案!
![](https://filescdn.proginn.com/47ef6639f41a6461df6532c588260356/48f9b77af47795ed7580446982ba4547.webp)
绝绝子,画框架图就用这个工具