0
点赞
收藏
分享

微信扫一扫

Spring Developer Tools 源码分析:一、文件目录监控设计


Spring Developer Tools 源码分析

Spring DevTools 介绍 ​

Spring Developer Tools,后续简称为 devtools,这个工具不仅好用,而且在这个工具的源码中,也有很多非常有学习价值的设计,本系列会逐个分析 devtools,最后使得阅读本文的读者不仅能了解 devtools 的实现,还能学会 devtools 的一些设计思想。

devtools 功能实现的基础就是检测代码变化,所以先从这个比较独立的部分开始。

一、文件目录监控设计

从 JDK 7 开始,Java 提供了 ​​java.nio.file.WatchService​​ 用于文件监控, Commons IO 也提供了 ​​org.apache.commons.io.monitor.FileAlterationObserver​​ 用于监控目录。

devtools 自己实现了简单文件监控,通过独立线程,在一定的时间间隔内,对监控的目录做一次快照,然后对比前后两次快照,对比文件的信息来判断文件是新增、修改还是删除。

下图是文件监控相关的类的类间关系图:

Spring Developer Tools 源码分析:一、文件目录监控设计_spring

下面按类来介绍。

1.1 Type 枚举,变化状态

在这个枚举类中定义了文件变化的三种状态:​​ADD,MODIFY,DELETE​​。

1.2 ChangedFile,变化的文件

该类记录了文件所在的资源目录,文件本身,文件的变化状态。

1.3 ChangedFiles,变化文件的集合

该类是 ChangedFile 的一个集合。包含的都是同一个资源目录下变化的文件。

1.4 FileSnapshot 文件快照

记录单个文件的信息,包含是否存在,文件长度,修改时间。

1.5 FolderSnapshot 目录快照

一个资源目录会对应一个目录快照,通过目录创建 FolderSnapshot 时,会递归遍历所有子目录,获取内部的所有文件,所有具体的文件都对应一个 FileSnapshot 文件快照。

该类还提供了下面的方法:

public ChangedFiles getChangedFiles(FolderSnapshot snapshot,
FileFilter triggerFilter)

通过该方法可以对比两个目录快照,获得差异文件,也就是 1.3 中的 ChangedFiles。方法逻辑比较简单,首先比较的两个快照必须是相同的目录 (​​File​​​ 类 ​​equals​​​ 时,只要是相同的路径就相等),当前的实例(​​this​​​)是早的快照,传入的 ​​snapshot​​ 是最新的快照,通过对比,如果前一个没有,而新的有,就是新增的文件。反之就是删除,如果两个快照的各项属性中有不相同的,就是修改

该方法中的 ​​triggerFilter​​(触发文件) 用于实现当指定的文件变化时,才会重启的功能。

前面这 5 个类都是很简单的类,只有 FolderSnapshot 包含了稍微复杂的逻辑。

现在只要我们能拿到两个不同时间的目录快照,就能对比出变化的文件。

1.6 FileSystemWatcher 文件系统监控

这个类提供了一些可配置的参数,用于控制监控周期,监控次数等等。默认情况下的监控次数(​​remainingScans​​​)为 ​​-1​​,也就是不限制次数,会不停的通过轮询监控目录。

这个类中最主要的代码就是 ​​start​​ 方法:

/**
* Start monitoring the source folder for changes.
*/
public void start() {
synchronized (this.monitor) {
saveInitialSnapshots();
if (this.watchThread == null) {
Map<File, FolderSnapshot> localFolders = new HashMap<>();
localFolders.putAll(this.folders);
this.watchThread = new Thread(new Watcher(this.remainingScans,
new ArrayList<>(this.listeners), this.triggerFilter,
this.pollInterval, this.quietPeriod, localFolders));
this.watchThread.setName("File Watcher");
this.watchThread.setDaemon(this.daemon);
this.watchThread.start();
}
}
}

这里创建了一个 watchThread 去执行 ​​Watcher​​​,任务,具体的内容在下面的 ​​Watcher​​ 类中。

1.7 Watcher 监控类

Watcher 类是整个监控设计的核心,先看主要的 ​​run​​ 方法:

@Override
public void run() {
int remainingScans = this.remainingScans.get();
while (remainingScans > 0 || remainingScans == -1) {
try {
if (remainingScans > 0) {
this.remainingScans.decrementAndGet();
}
scan();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
remainingScans = this.remainingScans.get();
}
}

注意前面创建工作线程时的参数,​​remainingScans​​​ 默认值是 ​​-1​​​,在这里的 ​​while​​​ 判断中,​​-1​​​ 的值一直不会变,循环一直会执行。再看具体的 ​​scan​​ 方法:

private void scan() throws InterruptedException {
Thread.sleep(this.pollInterval - this.quietPeriod);
Map<File, FolderSnapshot> previous;
Map<File, FolderSnapshot> current = this.folders;
do {
previous = current;
current = getCurrentSnapshots();
Thread.sleep(this.quietPeriod);
}
while (isDifferent(previous, current));
if (isDifferent(this.folders, current)) {
updateSnapshots(current.values());
}
}

这个方法默认会等待 ​​1000-400​​​ 毫秒,然后调用 ​​getCurrentSnapshots​​​(结果是有序的),再等待 ​​400​​​ 毫秒,如果目录没有变化(​​isDifferent == false​​​),就会一直按照 ​​400​​​ 毫秒轮询。如果文件发生了变化,就调用 ​​updateSnapshots​​ 方法,这里没有复杂的逻辑,就不细说各个方法了。

再看 ​​updateSnapshots​​ 方法:

private void updateSnapshots(Collection<FolderSnapshot> snapshots) {
Map<File, FolderSnapshot> updated = new LinkedHashMap<>();
Set<ChangedFiles> changeSet = new LinkedHashSet<>();
for (FolderSnapshot snapshot : snapshots) {
FolderSnapshot previous = this.folders.get(snapshot.getFolder());
updated.put(snapshot.getFolder(), snapshot);
ChangedFiles changedFiles = previous.getChangedFiles(snapshot,
this.triggerFilter);
if (!changedFiles.getFiles().isEmpty()) {
changeSet.add(changedFiles);
}
}
if (!changeSet.isEmpty()) {
fireListeners(Collections.unmodifiableSet(changeSet));
}
this.folders = updated;
}

private void fireListeners(Set<ChangedFiles> changeSet) {
for (FileChangeListener listener : this.listeners) {
listener.onChange(changeSet);
}
}

这里通过对比项目目录的两个快照,找出其中的差异文件,得到 ​​Set<ChangedFiles>​​​,然后将差异文件作为参数,调用 ​​onChange​​​ 监听方法。此时所有注册的 ​​listener​​ 都会收到文件变化的通知。

后续我们继续看 devtools 如何使用 ​​FileSystemWatcher​​ 对类路径进行监控。


举报

相关推荐

0 条评论