Java可以如何實現文件的監聽
logback.xml
這個文件,而且生產環境下,直接去修改logback.xml文件中的日誌級別,不用重啟應用就可以生效 那麽,這個功能是怎麽實現的呢?應用中使用logback作為日誌輸出組件的話,大部分會去配置 logback.xml 這個文件,而且生產環境下,直接去修改logback.xml文件中的日誌級別,不用重啟應用就可以生效
那麽,這個功能是怎麽實現的呢?
I. 問題描述及分析
針對上面的這個問題,首先拋出一個實際的case,在我的個人網站 Z+中,所有的小工具都是通過配置文件來動態新增和隱藏的,因為只有一臺服務器,所以配置文件就簡化的直接放在了服務器的某個目錄下
現在的問題時,我需要在這個文件的內容發生變動時,應用可以感知這種變動,並重新加載文件內容,更新應用內部緩存
一個最容易想到的方法,就是輪詢,判斷文件是否發生修改,如果修改了,則重新加載,並刷新內存,所以主要需要關心的問題如下:
如何輪詢?
如何判斷文件是否修改?
配置異常,會不會導致服務不可用?(即容錯,這個與本次主題關聯不大,但又比較重要...)
II. 設計與實現
問題抽象出來之後,對應的解決方案就比較清晰了
如何輪詢 ? --》 定時器 Timer, ScheduledExecutorService 都可以實現
如何判斷文件修改? --》根據 java.io.File#lastModified 獲取文件的上次修改時間,比對即可
public class FileUpTest {
private long lastTime;
@Test
public void testFileUpdate() {
File file = new File("/tmp/alarmConfig");
// 首先文件的最近一次修改時間戳 lastTime = file.lastModified(); // 定時任務,每秒來判斷一下文件是否發生變動,即判斷lastModified是否改變 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (file.lastModified() > lastTime) { System.out.println("file update! time : " + file.lastModified()); lastTime = file.lastModified(); } } },0, 1, TimeUnit.SECONDS); try { Thread.sleep(1000 * 60); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
上面這個屬於一個非常簡單,非常基礎的實現了,基本上也可以滿足我們的需求,那麽這個實現有什麽問題呢?[好看的美女圖片](http://www.4587.com/)
定時任務的執行中,如果出現了異常會怎樣?
對上面的代碼稍作修改
public class FileUpTest {
private long lastTime;
private void ttt() {
throw new NullPointerException();
}
@Test
public void testFileUpdate() {
File file = new File("/tmp/alarmConfig");
lastTime = file.lastModified();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (file.lastModified() > lastTime) {
System.out.println("file update! time : " + file.lastModified());
lastTime = file.lastModified();
ttt();
}
}
}, 0, 1, TimeUnit.SECONDS);
try {
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
實際測試,發現只有首次修改的時候,觸發了上面的代碼,但是再次修改則沒有效果了,即當拋出異常之後,定時任務將不再繼續執行了,這個問題的主要原因是因為 ScheduledExecutorService 的原因了
直接查看ScheduledExecutorService的源碼註釋說明
If any execution of the task encounters an exception, subsequent executions are suppressed.Otherwise, the task will only terminate via cancellation or termination of the executor. 即如果定時任務執行過程中遇到發生異常,則後面的任務將不再執行。
所以,使用這種姿勢的時候,得確保自己的任務不會拋出異常,否則後面就沒法玩了
對應的解決方法也比較簡單,整個catch一下就好
III. 進階版
前面是一個基礎的實現版本了,當然在java圈,基本上很多常見的需求,都是可以找到對應的開源工具來使用的,當然這個也不例外,而且應該還是大家比較屬性的apache系列
首先maven依賴
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
主要是借助這個工具中的 FileAlterationObserver, FileAlterationListener, FileAlterationMonitor 三個類來實現相關的需求場景了,當然使用也算是很簡單了,以至於都不太清楚可以再怎麽去說明了,直接看下面從我的一個開源項目quick-alarm中拷貝出來的代碼
?
public class PropertiesConfListenerHelper {
public static boolean registerConfChangeListener(File file, Function<File, Map<String, AlarmConfig>> func) {
try {
// 輪詢間隔 5 秒
long interval = TimeUnit.SECONDS.toMillis(5);
// 因為監聽是以目錄為單位進行的,所以這裏直接獲取文件的根目錄
File dir = file.getParentFile();
// 創建一個文件觀察器用於過濾
FileAlterationObserver observer = new FileAlterationObserver(dir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(),
FileFilterUtils.nameFileFilter(file.getName())));
//設置文件變化監聽器
observer.addListener(new MyFileListener(func));
FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
monitor.start();
return true;
} catch (Exception e) {
log.error("register properties change listener error! e:{}", e);
return false;[**美女圖片大全**](http://www.4587.com/)
}
}
static final class MyFileListener extends FileAlterationListenerAdaptor {
private Function<File, Map<String, AlarmConfig>> func;
public MyFileListener(Function<File, Map<String, AlarmConfig>> func) {
this.func = func;
}
@Override
public void onFileChange(File file) {
Map<String, AlarmConfig> ans = func.apply(file); // 如果加載失敗,打印一條日誌
log.warn("PropertiesConfig changed! reload ans: {}", ans);
}
}
}
針對上面的實現,簡單說明幾點:
這個文件監聽,是以目錄為根源,然後可以設置過濾器,來實現對應文件變動的監聽
如上面registerConfChangeListener方法,傳入的file是具體的配置文件,因此構建參數的時候,撈出了目錄,撈出了文件名作為過濾
第二參數是jdk8語法,其中為具體的讀取配置文件內容,並映射為對應的實體對象
一個問題,如果 func方法執行時,也拋出了異常,會怎樣?
實際測試表現結果和上面一樣,拋出異常之後,依然跪,所以依然得註意,不要跑異常
那麽簡單來看一下上面的實現邏輯,直接扣出核心模塊
23
public void run() {
while(true) {
if(this.running) {
Iterator var1 = this.observers.iterator();
while(var1.hasNext()) {
FileAlterationObserver observer = (FileAlterationObserver)var1.next();
observer.checkAndNotify();
}
if(this.running) {
try {
Thread.sleep(this.interval);
} catch (InterruptedException var3) {
;
}
continue;
}
}
return;
}
}
從上面基本上一目了然,整個的實現邏輯了,和我們的第一種定時任務的方法不太一樣,這兒直接使用線程,死循環,內部采用sleep的方式來來暫停,因此出現異常時,相當於直接拋出去了,這個線程就跪了
補充JDK版本
jdk1.7,提供了一個WatchService,也可以用來實現文件變動的監聽,之前也沒有接觸過,才知道有這個東西,然後搜了一下使用相關,發現也挺簡單的,看到有博文說明是基於事件驅動式的美女圖片大全,效率更高,下面也給出一個簡單的示例demo
?@Test
public void testFileUpWather() throws IOException {
// 說明,這裏的監聽也必須是目錄
Path path = Paths.get("/tmp");
WatchService watcher = FileSystems.getDefault().newWatchService();
path.register(watcher, ENTRY_MODIFY);
new Thread(() -> {
try {
while (true) {
WatchKey key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == OVERFLOW) {
//事件可能lost or discarded
continue;
}
Path fileName = (Path) event.context();
System.out.println("文件更新: " + fileName);
}
if (!key.reset()) { // 重設WatchKey
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
try {
Thread.sleep(1000 60 10);
} catch (InterruptedException e) {e.printStackTrace();
}
}
@Test
br/>e.printStackTrace();
}
}
@Test
// 說明,這裏的監聽也必須是目錄
Path path = Paths.get("/tmp");
WatchService watcher = FileSystems.getDefault().newWatchService();
path.register(watcher, ENTRY_MODIFY);
new Thread(() -> {
try {
while (true) {
WatchKey key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == OVERFLOW) {
//事件可能lost or discarded
continue;
}
Path fileName = (Path) event.context();
System.out.println("文件更新: " + fileName);
}
if (!key.reset()) { // 重設WatchKey
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
try {
Thread.sleep(1000 60 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
IV. 小結
使用Java來實現配置文件變動的監聽,主要涉及到的就是兩個點
如何輪詢: 定時器(Timer, ScheduledExecutorService), 線程死循環+sleep
文件修改: File#lastModified
整體來說,這個實現還是比較簡單的,無論是自定義實現,還是依賴 commos-io來做,都沒太大的技術成本,美女圖片大全但是需要註意的一點是:
千萬不要在定時任務 or 文件變動的回調方法中拋出異常!!!
為了避免上面這個情況,一個可以做的實現是借助EventBus的異步消息通知來實現,當文件變動之後,發送一個消息即可,然後在具體的重新加載文件內容的方法上,添加一個 @Subscribe註解即可,這樣既實現了解耦,也避免了異常導致的服務異常
Java可以如何實現文件的監聽