写点什么

spring-boot-devtools 快速重启的秘密

作者:4ye
  • 2021 年 11 月 30 日
  • 本文字数:3277 字

    阅读完需:约 11 分钟

上文写了 AOP 插件 后,咱们也了解到这个 ClassLoader 的重要,所以今天咱们来聊聊这个热部署神器 spring-boot-devtools 的源码,看看它是怎么用这个 ClassLoader 来实现快速重启,帮我们节省时间的!😝

文章概要

文章的主旋律如下👇


spring.factories

在了解了 Springboot 的自动装配原理 后(不了解可以看看 4ye 之前写的这篇 👉 《Springboot自动装配原理探索》),我们直接打开 spring-boot-devtools 源码 ,找到 spring.factories 文件,



我们一般都本地开发调试的,所以就直接看这个 LocalDevToolsAutoConfiguration 类啦😋

LocalDevToolsAutoConfiguration

可以看到核心点在 重启和重载 👇



主角👇


RestartConfiguration 源码如下 👇


/** * Local Restart Configuration. */@Lazy(false)@Configuration(proxyBeanMethods = false)@ConditionalOnProperty(prefix = "spring.devtools.restart", name = "enabled", matchIfMissing = true)static class RestartConfiguration {
private final DevToolsProperties properties;
RestartConfiguration(DevToolsProperties properties) { this.properties = properties; }
@Bean ApplicationListener<ClassPathChangedEvent> restartingClassPathChangedEventListener( FileSystemWatcherFactory fileSystemWatcherFactory) { return (event) -> { if (event.isRestartRequired()) { Restarter.getInstance().restart(new FileWatchingFailureHandler(fileSystemWatcherFactory)); } }; }
@Bean @ConditionalOnMissingBean ClassPathFileSystemWatcher classPathFileSystemWatcher(FileSystemWatcherFactory fileSystemWatcherFactory, ClassPathRestartStrategy classPathRestartStrategy) { URL[] urls = Restarter.getInstance().getInitialUrls(); ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(fileSystemWatcherFactory, classPathRestartStrategy, urls); watcher.setStopWatcherOnRestart(true); return watcher; }
@Bean @ConditionalOnMissingBean ClassPathRestartStrategy classPathRestartStrategy() { return new PatternClassPathRestartStrategy(this.properties.getRestart().getAllExclude()); }
@Bean FileSystemWatcherFactory fileSystemWatcherFactory() { return this::newFileSystemWatcher; }
@Bean @ConditionalOnProperty(prefix = "spring.devtools.restart", name = "log-condition-evaluation-delta", matchIfMissing = true) ConditionEvaluationDeltaLoggingListener conditionEvaluationDeltaLoggingListener() { return new ConditionEvaluationDeltaLoggingListener(); }
private FileSystemWatcher newFileSystemWatcher() { Restart restartProperties = this.properties.getRestart(); FileSystemWatcher watcher = new FileSystemWatcher(true, restartProperties.getPollInterval(), restartProperties.getQuietPeriod()); String triggerFile = restartProperties.getTriggerFile(); if (StringUtils.hasLength(triggerFile)) { watcher.setTriggerFilter(new TriggerFileFilter(triggerFile)); } List<File> additionalPaths = restartProperties.getAdditionalPaths(); for (File path : additionalPaths) { watcher.addSourceDirectory(path.getAbsoluteFile()); } return watcher; }
}
复制代码


我们先来看看这个 重启 中有什么叭😄

重启原理介绍

大概这么一个思路👇 下面就跟着源码分析啦😄(文末有源码重启要点流程图


RestartConfiguration

有这么些方法👇



从名字上分析,这两个方法应该是重点,逻辑上应该是 有一个 watcher 在盯着 classpath ,如果有变动的话,就触发这个 ClassPathChangedEvent 事件 😝


那么看看这个 watcher 叭 😄

ClassPathFileSystemWatcher


可以看到这里就创建了这个 ClassPathFileSystemWatcher 类 👇



这里我们注意到它实现了三个接口,经过前面 Spring 文章的学习,咱们知道第一步就该看啥了😄


👉 《16张图解锁Spring的整体脉络》


根据类的初始化,先看看有 static 相关的代码没,接着看 构造器 ,最后就来到这个初始化方法 afterPropertiesSet 啦😄


这里没有 static 方法,构造器也很简单,就是获取 FileSystemWatcherFactoryClassPathRestartStrategy 和 监视的文件路径,那么就看看 afterPropertiesSet 写了什么叭 👇


ClassPathFileChangeListener

这个也不复杂,就监听到文件改变后,发布事件 ClassPathChangedEvent


FileSystemWatcher

接着就是这个 start 方法啦👇


很明显就是开启一个线程,那么咱们来看看线程中到底在 run 什么🐷



找到这个任务类 Watcher 👇



可以发现它的任务就是一直 scanpollInterval 默认是 1squietPeriod 默认是 0.4s


意思是每次轮询的时间是 1s ,包含中间休息的 0.4s ,休息事件是来确认文件在这个期间没有再次被改动。


改动了的话会回调 FileChangeListeneronChange ,对应我们上面的这个 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 事件机制 无处不在的身影,还有各种初始化的操作,以及线程,并发,锁在重启过程中的使用,这些就需要小伙伴们打开源码自身感受了,如 守护线程ReentrantLockCopyOnWriteArrayListCountDownLatch ,甚至 OOM 都能这么用!


还有 重启 原来就是 反射调用 main 方法 呀🐷

重启过程源码要点



最后

本文就分享到这里啦🐖


仓库地址 👇 (感谢每一颗 star !


https://github.com/Java4ye/springboot-demo-4ye


喜欢的话可以 关注星标 下公众号 Java4ye 支持下 4ye 呀😝,这样就可以第一时间收到更文消息啦🐷


我是 4ye 咱们下期应该……很快再见!! 😆

发布于: 3 小时前阅读数: 6
用户头像

4ye

关注

公众号:J a v a 4 y e 2021.07.19 加入

定个小目标,写个三年~ 分享一个普通程序员的技术生涯,生活点滴,为了早点睡觉而学习🐷。

评论

发布
暂无评论
spring-boot-devtools 快速重启的秘密