文件系統監控在配置熱加載、日誌分析、上傳文件自動處理、熱部署等場景剛需。Java 7+ 提供 WatchService,但 API 底層、樣板代碼多、跨平台一致性一般。Apache Commons IO FileAlterationMonitor 封裝了輪詢與事件回調,配合 Spring 生命周期管理,工程落地極簡。本文提供生產級完整實現:配置、組件分層、啟停掛鉤、過濾鏈、事件處理擴展點。
┌─────────────────────────────────────┐
│ Spring 容器生命周期 │
├─────────────────────────────────────┤
│ FileMonitor (監控器) │ ← 管理輪詢間隔、線程池、Observer 註冊
│ ├─ FileAlterationMonitor (Commons IO)│
│ └─ ThreadFactory (守護線程命名) │
├─────────────────────────────────────┤
│ FileObserver (觀察器) │ ← 綁定目錄、過濾器、Listener
│ └─ FileAlterationObserver │
├─────────────────────────────────────┤
│ FileListener (監聽器) │ ← 業務邏輯入口:onFileCreate/Change/Delete
│ └─ FileAlterationListenerAdaptor │
├─────────────────────────────────────┤
│ FileFilter (過濾器) │ ← 擴展名白名單、大小、正則等靈活規則
│ └─ FileFilter (Commons IO) │
└─────────────────────────────────────┘
<beans ...>
<!-- 文件監控器:輪詢間隔 10s -->
<bean id="fileMonitor" class="com.example.monitor.FileMonitorImpl">
<constructor-arg index="0" value="10000"/> <!-- ms -->
<constructor-arg index="1" ref="fileObserver"/>
</bean>
<!-- 觀察器:監聽 D:/UploadDir,僅處理 .xml 文件 -->
<bean id="fileObserver" class="com.example.monitor.FileObserverImpl">
<constructor-arg index="0" value="D:/UploadDir"/>
<constructor-arg index="1" ref="fileFilter"/>
<constructor-arg index="2" ref="fileListener"/>
</bean>
<!-- 監聽器:業務邏輯 -->
<bean id="fileListener" class="com.example.monitor.FileListener"/>
<!-- 過濾器:支持多擴展名,逗號分隔 -->
<bean id="fileFilter" class="com.example.monitor.FileFilterImpl">
<constructor-arg index="0" value="xml,txt,properties"/>
</bean>
</beans>
public class MonitorLifecycleListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent event) {
super.contextInitialized(event);
FileMonitor monitor = getCurrentWebApplicationContext().getBean(FileMonitor.class);
monitor.start();
}
@Override
public void contextDestroyed(ServletContextEvent event) {
FileMonitor monitor = getCurrentWebApplicationContext().getBean(FileMonitor.class);
monitor.stop();
super.contextDestroyed(event);
}
}
web.xml 中註冊:
<listener>
<listener-class>com.example.MonitorLifecycleListener</listener-class>
</listener>
public interface FileMonitor {
void addObserver(FileAlterationObserver observer);
void removeObserver(FileAlterationObserver observer);
Iterable<FileAlterationObserver> getObservers();
void start();
void stop();
long getInterval();
}
public class FileMonitorImpl implements FileMonitor {
private final FileAlterationMonitor monitor;
private static final String THREAD_NAME = "File-Monitor-Daemon";
public FileMonitorImpl(int interval, FileAlterationObserver observer) {
this(interval, observer, new BasicThreadFactory.Builder()
.namingPattern(THREAD_NAME).daemon(true).build());
}
public FileMonitorImpl(int interval, FileAlterationObserver observer, ThreadFactory factory) {
this.monitor = new FileAlterationMonitor(interval, new FileAlterationObserver[]{observer});
this.monitor.setThreadFactory(factory);
}
// 委托實現...
}
public class FileObserverImpl extends FileAlterationObserver implements FileObserver {
private final FileFilter filter;
public FileObserverImpl(String dir, FileFilter filter, FileAlterationListener listener) {
super(dir, filter, IOCase.INSENSITIVE); // Windows 不區分大小寫
addListener(listener);
this.filter = filter;
ensureDirectoryExists(dir);
}
private void ensureDirectoryExists(String dir) {
File d = new File(dir);
if (!d.exists()) FileUtils.forceMkdir(d);
else if (d.isFile()) { FileUtils.forceDelete(d); FileUtils.forceMkdir(d); }
}
}
public class FileListener extends FileAlterationListenerAdaptor {
private static final Logger log = LoggerFactory.getLogger(FileListener.class);
@Override public void onFileCreate(File file) {
log.info("文件創建: {}", file.getAbsolutePath());
processFile(file, EventType.CREATE);
}
@Override public void onFileChange(File file) {
log.info("文件變更: {}", file.getAbsolutePath());
processFile(file, EventType.MODIFY);
}
@Override public void onFileDelete(File file) {
log.warn("文件刪除: {}", file.getAbsolutePath());
// 刪除事件通常不處理內容,僅記錄或清理緩存
}
private void processFile(File file, EventType type) {
// 異步提交線程池,避免阻塞監控線程
// 解析 XML、發送消息隊列、觸發配置刷新等
}
}
public class FileFilterImpl implements FileFilter {
private final String[] extensions;
public FileFilterImpl(String... extensions) {
this.extensions = extensions;
}
@Override
public boolean accept(File pathname) {
return FilenameUtils.isExtension(pathname.getName(), extensions);
}
@Override public String[] getExtensions() { return extensions; }
}
| 決策點 | 選擇 | 理由 |
|---|---|---|
| 監控機制 | 輪詢 | 跨平台一致(Linux inotify / Windows ReadDirectoryChangesW / macOS FSEvents 行為差異大),Commons IO 內部已優化 |
| 輪詢間隔 | 10s (可配置) | 平衡實時性與 CPU/磁盤 IO;業務允許秒級延遲 |
| 線程模型 | 單守護線程 + 觀察器回調 | 簡單、資源佔用極低;事件處理建議異步化 |
| 目錄不存在 | 自動創建 | 避免部署順序依賴,增強彈性 |
| 大小寫敏感 | IOCase.INSENSITIVE | 跨平台一致行為 |
onFileChange,建議在 Listener 內用 Guava RateLimiter 或時間窗口去重.part 臨時文件創建/變更,待 .complete 標記文件出現後再處理WatchService (事件驅動、無輪詢)、JNotify (JNI 原生)、Spring Boot FileSystemWatcher (內部封裝 WatchService)基於 Commons IO FileAlterationMonitor 的文件監控方案,以「約定優於配置」、Spring 原生整合、清晰分層、極低樣板代碼,成為中小型項目、單體應用、內部工具的首選。對於高實時性、海量文件、集群共享存儲等強一致性場景,建議遷移至 WatchService + 分布式協調方案。