在企業級應用中,常需監控特定目錄下的配置文件、上傳文件或日誌文件變化,自動觸發重載、解析、同步等業務邏輯。本文基於 Apache Commons IO 2.x 的 FileAlterationMonitor 組件,結合 Spring 容器生命週期,構建一套可配置、可擴展的文件監控框架。
採用觀察者模式解耦四大角色:
<!-- 1. 文件過濾器:僅監控 .xml 文件 -->
<bean id="fileFilter" class="com.example.monitor.FileFilterImpl">
<constructor-arg value="xml"/>
</bean>
<!-- 2. 文件監聽器:實現具體業務邏輯 -->
<bean id="fileListener" class="com.example.monitor.FileListener"/>
<!-- 3. 文件觀察器:綁定目錄 D:/UploadDir、過濾器、監聽器 -->
<bean id="fileObserver" class="com.example.monitor.FileObserverImpl">
<constructor-arg value="D:/UploadDir"/>
<constructor-arg ref="fileFilter"/>
<constructor-arg ref="fileListener"/>
</bean>
<!-- 4. 文件監測器:掃描間隔 10秒、掛載觀察器 -->
<bean id="fileMonitor" class="com.example.monitor.FileMonitorImpl">
<constructor-arg value="10000"/> <!-- 毫秒 -->
<constructor-arg ref="fileObserver"/>
</bean>
<!-- 5. 容器啟動/關閉時自動啟停監測器 -->
<bean class="com.example.monitor.SpringContextLoaderListener"/>
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);
}
// 委托實現 start/stop/addObserver/removeObserver/getInterval ...
}
public class FileObserverImpl extends FileAlterationObserver implements FileObserver {
private final FileFilter filter;
public FileObserverImpl(String directory, FileFilter filter, FileAlterationListener listener) {
super(directory, filter, (IOCase) null);
this.filter = filter;
addListener(listener);
ensureDirectoryExists(directory);
}
private void ensureDirectoryExists(String dir) {
File directory = new File(dir);
if (!directory.exists()) {
FileUtils.forceMkdir(directory);
} else if (directory.isFile()) {
FileUtils.forceDelete(directory);
FileUtils.forceMkdir(directory);
}
}
// 實現 FileObserver 接口方法:getDirectory/getFilter/initialize/destroy ...
}
public class FileListener extends FileAlterationListenerAdaptor {
@Override public void onFileCreate(File file) { /* 處理新文件 */ }
@Override public void onFileChange(File file) { /* 處理修改 */ }
@Override public void onFileDelete(File file) { /* 處理刪除 */ }
@Override public void onStart(FileAlterationObserver observer) { /* 掃描開始 */ }
@Override public void onStop(FileAlterationObserver observer) { /* 掃描停止 */ }
}
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; }
}
public class SpringContextLoaderListener extends ContextLoaderListener {
@Override public void contextInitialized(ServletContextEvent event) {
super.contextInitialized(event);
getScanner().start(); // 啟動監控
}
@Override public void contextDestroyed(ServletContextEvent event) {
getScanner().stop(); // 優雅關閉
super.contextDestroyed(event);
}
private FileMonitor getScanner() {
return getCurrentWebApplicationContext().getBean(FileMonitor.class);
}
}
在 web.xml 中註冊:
<listener>
<listener-class>com.example.monitor.SpringContextLoaderListener</listener-class>
</listener>
FileObserver,註冊到同一 FileMonitoronFileChange 中記錄上次處理時間,忽略短時間內重複觸發(編輯器保存常觸發多次)WatchService(NIO.2)提供內核級事件通知,延遲更低,但跨平台一致性稍弱,Commons IO 內部已自動優先使用 WatchServicesrc/main/java/com/example/monitor/
├── FileMonitor.java # 介面
├── FileMonitorImpl.java # 實現
├── FileObserver.java # 介面
├── FileObserverImpl.java # 實現
├── FileListener.java # 業務模板
├── FileFilter.java # 介面
├── FileFilterImpl.java # 實現
├── SpringContextLoaderListener.java
└── package-info.java
該方案零侵入業務代碼,通過 Spring 配置即可靈活調整監控目錄、間隔、文件類型、處理邏輯,適合配置熱加載、文件異步處理、增量同步等場景。生產環境建議加上監控指標(掃描耗時、事件處理延遲、錯誤計數)接入 Prometheus/Grafana 可觀測體系。