spring-boot-devtools 快速重启的秘密!
今天咱们来聊聊这个热部署神器 spring-boot-devtools 的源码,看看它是怎么用这个 ClassLoader 来实现快速重启,帮我们节省时间的!
文章概要
文章的主旋律如下
spring.factories
在了解了 Springboot 的自动装配原理 后(不了解可以看看 4ye 之前写的这篇 《Springboot自动装配原理探索》 ),我们直接打开 spring-boot-devtools 源码 ,找到 spring.factories 文件,
我们一般都本地开发调试的,所以就直接看这个 LocalDevToolsAutoConfiguration 类啦
LocalDevToolsAutoConfiguration
可以看到核心点在 重启和重载
主角
我们先来看看这个 重启 中有什么叭
重启原理介绍
大概这么一个思路 下面就跟着源码分析啦 ( 文末有源码重启要点流程图 )
RestartConfiguration
有这么些方法
从名字上分析,这两个方法应该是重点,逻辑上应该是 有一个 watcher 在盯着 classpath ,如果有变动的话,就触发这个 ClassPathChangedEvent 事件
那么看看这个 watcher 叭
ClassPathFileSystemWatcher
可以看到这里就创建了这个 ClassPathFileSystemWatcher 类
这里我们注意到它实现了三个接口,经过前面 Spring 文章的学习,咱们知道第一步就该看啥了
根据类的初始化,先看看有 static 相关的代码没,接着看 构造器 ,最后就来到这个初始化方法 afterPropertiesSet 啦
这里没有 static 方法,构造器也很简单,就是获取 FileSystemWatcherFactory , ClassPathRestartStrategy 和 监视的文件路径,那么就看看 afterPropertiesSet 写了什么叭
ClassPathFileChangeListener
这个也不复杂,就监听到文件改变后,发布事件 ClassPathChangedEvent
FileSystemWatcher
接着就是这个 start 方法啦
很明显就是开启一个线程,那么咱们来看看线程中到底在 run 什么
找到这个任务类 Watcher
可以发现它的任务就是一直 scan , pollInterval 默认是 1s , quietPeriod 默认是 0.4s
意思是每次轮询的时间是 1s ,包含中间休息的 0.4s ,休息事件是来确认文件在这个期间没有再次被改动。
改动了的话会回调 FileChangeListener 的 onChange ,对应我们上面的这个 ClassPathFileChangeListener ,会去发布事件 ClassPathChangedEvent
ApplicationListener
绕了一大圈,终于描述完了这个监视器 ClassPathFileSystemWatcher ,同时,我们也得把目光移到这个 RestartConfiguration 的第二个核心 监听器
如图所示,这个方法的作用就是 重启应用 restart
重启应用
重启的过程中呢,包括两个步骤,第一步 stop ,第二步 start
stop 部分就是毁灭这些东西了,这里也藏了很多细节,有很多并发相关的知识点
比如
一. ReentrantLock 是写在 try catch 的里面还是外面?
二. 循环里的 rootContexts 其实是 CopyOnWriteArrayList 类型的
三. 通过强制的 OOM 来清除所有的 软/弱引用 ( 还有这种操作的!)
在 start 的过程中,是通过创建这个 重启线程 RestartLauncher 来实现的,可以发现该类的任务就是找到 mainclass 并调用 main 方法,完成重启。
而在这个过程中,就涉及到这个 classloader 啦。
ClassLoader
细心的小伙伴可以发现上面这行代码中,调用到了这个 ClassLoader ,这个 getContextClassLoader() 是属于 Thread 类的,通过它可以获取到当前线程上下文的 ClassLoader 。
Class.forName(this.mainClassName, false, getContextClassLoader());
在创建这个 RestartLauncher 线程时,就已经将咱们这个 RestartClassLoader 给传进来了。
重启时,就直接通过 RestartClassLoader 去找到 main 方法,完成重启。
很明显这里 破坏了双亲委派机制,先从自身查找,没有的话再去父类查找
这里 业务代码 都被 RestartClassLoader 加载了,而每次重启都会重新创建这个 RestartClassLoader ,然后去加载业务代码 (通过传进来的 URL 可以发现)
那么到此,这个 重启 的过程就完成了。
差点忘了,这里还有个默认的监视范围
监视策略
如下图 默认策略中,这些路径下的文件变化 不被检测
可通过配置修改
spring.devtools.restart.exclude=static/**,public/**
总结
通过阅读源码,我们知道了 spring-boot-devtools 是通过自定义 RestartClassLoader 来加载业务代码,并在重启时销毁它,再重新创建,进而重新获取代码,实现这个快速重启的。
而其他 jar 包等由另外的 ClassLoader 加载,不受影响。
同时,也可以看到 Spring 事件机制 无处不在的身影,还有各种初始化的操作,以及 线程,并发,锁 在重启过程中的使用,这些就需要小伙伴们打开源码自身感受了,如 守护线程 , ReentrantLock , CopyOnWriteArrayList , CountDownLatch ,甚至 OOM 都能这么用!
还有 重启 原来就是 反射调用 main 方法 呀