• 让天下没有难学的技术
    多数学员都来自推荐,这就是口碑的力量

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

spring.factories

在了解了 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 事件

ClassPathFileSystemWatcher

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

这里我们注意到它实现了三个接口,经过前面 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 方法

重启过程源码要点

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注