在 Spring Developer Tools 源码分析:一、文件目录监控设计 介绍了 devtools 提供的文件监控实现,在第二部分中,我们将会使用第一部分提供的目录监控功能,实现对开发环境中 classpath 的监控。
首先看一些这一部分可能涉及到的类图:
在图中,红色斜线左上部分是第一部分中介绍的文件目录监控的类,其中 FileSystemWatcher
会通过独立线程监控指定的目录,当目录内容发生变化时,通过对比快照可以获得所有监控目录变化的文件 ChangedFiles
,然后将变化通知给实现了 FileChangeListener
监听的订阅者。
下面按照局部到整体的顺序介绍主要的类。
这个类实现了 Iterable<File>
接口,构造方法的参数为 URL[]
,也就是类路径的 URL 形式。ClassPathFolders 就是简单的把 URL 转换为了 List<File>
集合,最后通过 iterator
返回迭代器。
@FunctionalInterface public interface ClassPathRestartStrategy { /** * Return true if a full restart is required. * @param file the changed file * @return {@code true} if a full restart is required */ boolean isRestartRequired(ChangedFile file); }
接口方法根据变化的文件来决定是否需要重启。
默认提供了 PatternClassPathRestartStrategy
实现,这个实现支持 Ant 风格的模式匹配 ,通过设置 excludePatterns
类设置不需要重启的文件名(无法设置路径)。不在排除范围内的文件发生变化时,就会返回 true
来引起后续的重启。
该类继承了 ApplicationEvent
,事件中包含变化的文件集合以及系统是否需要重启的标志。该类在后面的 ClassPathFileChangeListener
中,会将监控目录发生变化的消息转换为 Spring 的事件,转换后就可以方便的通过 @EventListener
注解进一步解耦事件监听。
这个类实现了 FileChangeListener
,并且会在后面的 ClassPathFileSystemWatcher
中添加到 FileSystemWatcher
的订阅列表中。
当文件变化时,就会触发下面的方法:
@Override public void onChange(Set<ChangedFiles> changeSet) { boolean restart = isRestartRequired(changeSet); publishEvent(new ClassPathChangedEvent(this, changeSet, restart)); }
这里先使用前面提到过的重启策略判断此次变化是否需要重启,然后创建一个 ClassPathChangedEvent
事件,通过 Spring 的 ApplicationEventPublisher
发布出去。发布事件后,所有监听 ClassPathChangedEvent
事件的监听器都会触发执行,在后续博客中会通过对该事件的监听和这里建立联系。
类路径监控的实现类为 ClassPathFileSystemWatcher
,先看这个类的构造方法:
/** * Create a new {@link ClassPathFileSystemWatcher} instance. * @param fileSystemWatcherFactory a factory to create the underlying * {@link FileSystemWatcher} used to monitor the local file system * @param restartStrategy the classpath restart strategy * @param urls the URLs to watch */ public ClassPathFileSystemWatcher(FileSystemWatcherFactory fileSystemWatcherFactory, ClassPathRestartStrategy restartStrategy, URL[] urls) { Assert.notNull(fileSystemWatcherFactory, "FileSystemWatcherFactory must not be null"); Assert.notNull(urls, "Urls must not be null"); this.fileSystemWatcher = fileSystemWatcherFactory.getFileSystemWatcher(); this.restartStrategy = restartStrategy; this.fileSystemWatcher.addSourceFolders(new ClassPathFolders(urls)); }
创建该类时,需要提供 FileSystemWatcher 的工厂类, PatternClassPathRestartStrategy
重启策略类以及要监控的类路径 URL[]
。
在构造方法中,通过工厂类获取了 fileSystemWatcher
,设置了当前的重启策略,然后通过 ClassPathFolders
包装了 URL[]
数组。然后设置 fileSystemWatcher
监控这些目录( fileSystemWatcher
通过 Iterator
接口和 ClassPathFolders
解耦)。
ClassPathFileSystemWatcher
还实现了 InitializingBean
接口和 ApplicationContextAware
接口,其中 setApplicationContext
方法会在 afterPropertiesSet
方法前执行,两个方法的实现如下:
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void afterPropertiesSet() throws Exception { if (this.restartStrategy != null) { FileSystemWatcher watcherToStop = null; if (this.stopWatcherOnRestart) { watcherToStop = this.fileSystemWatcher; } this.fileSystemWatcher.addListener(new ClassPathFileChangeListener( this.applicationContext, this.restartStrategy, watcherToStop)); } this.fileSystemWatcher.start(); }
虽然这里会判断 restartStrategy
,但是 devtools 默认配置时是提供该策略的,不管你是否配置了排除目录,都会提供这个策略,只有提供了这个策略,才会有 ClassPathFileChangeListener
,后续监听 ClassPathChangedEvent
事件才能起作用。在所有Bean属性设置好后( afterPropertiesSet
), this.fileSystemWatcher.start()
就启动了 。
此时类路径已经被监控了,后续我们需要知道 ClassPathFileSystemWatcher
是何时创建的, ClassPathChangedEvent
在何处监听的,当发生变化后,后续要怎样继续执行。
未完待续…