一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - JAVA教程 - Java可以如何實現文件變動的監聽的示例

Java可以如何實現文件變動的監聽的示例

2021-04-04 14:07小灰灰Blog JAVA教程

本篇文章主要介紹了Java可以如何實現文件變動的監聽的示例,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

應用中使用logback作為日志輸出組件的話,大部分會去配置 `logback.xml` 這個文件,而且生產環境下,直接去修改logback.xml文件中的日志級別,不用重啟應用就可以生效 那么,這個功能是怎么實現的呢?

應用中使用logback作為日志輸出組件的話,大部分會去配置 logback.xml 這個文件,而且生產環境下,直接去修改logback.xml文件中的日志級別,不用重啟應用就可以生效

那么,這個功能是怎么實現的呢?

I. 問題描述及分析

針對上面的這個問題,首先拋出一個實際的case,在我的個人網站 Z+中,所有的小工具都是通過配置文件來動態新增和隱藏的,因為只有一臺服務器,所以配置文件就簡化的直接放在了服務器的某個目錄下

現在的問題時,我需要在這個文件的內容發生變動時,應用可以感知這種變動,并重新加載文件內容,更新應用內部緩存

一個最容易想到的方法,就是輪詢,判斷文件是否發生修改,如果修改了,則重新加載,并刷新內存,所以主要需要關心的問題如下:

  1. 如何輪詢?
  2. 如何判斷文件是否修改?
  3. 配置異常,會不會導致服務不可用?(即容錯,這個與本次主題關聯不大,但又比較重要...)

II. 設計與實現

問題抽象出來之后,對應的解決方案就比較清晰了

  1. 如何輪詢 ? --》 定時器 Timer, ScheduledExecutorService 都可以實現
  2. 如何判斷文件修改? --》根據 java.io.File#lastModified 獲取文件的上次修改時間,比對即可

那么一個很簡單的實現就比較容易了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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();
    }
  }
}

上面這個屬于一個非常簡單,非常基礎的實現了,基本上也可以滿足我們的需求,那么這個實現有什么問題呢?

定時任務的執行中,如果出現了異常會怎樣?

對上面的代碼稍作修改

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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依賴

?
1
2
3
4
5
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.6</version>
</dependency>

主要是借助這個工具中的 FileAlterationObserver, FileAlterationListener, FileAlterationMonitor 三個類來實現相關的需求場景了,當然使用也算是很簡單了,以至于都不太清楚可以再怎么去說明了,直接看下面從我的一個開源項目quick-alarm中拷貝出來的代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;
    }
  }
 
 
  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);
    }
  }
}

針對上面的實現,簡單說明幾點:

  1. 這個文件監聽,是以目錄為根源,然后可以設置過濾器,來實現對應文件變動的監聽
  2. 如上面registerConfChangeListener方法,傳入的file是具體的配置文件,因此構建參數的時候,撈出了目錄,撈出了文件名作為過濾
  3. 第二參數是jdk8語法,其中為具體的讀取配置文件內容,并映射為對應的實體對象

一個問題,如果 func方法執行時,也拋出了異常,會怎樣?

實際測試表現結果和上面一樣,拋出異常之后,依然跪,所以依然得注意,不要跑異常

那么簡單來看一下上面的實現邏輯,直接扣出核心模塊

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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();
  }
}

IV. 小結

使用Java來實現配置文件變動的監聽,主要涉及到的就是兩個點

  1. 如何輪詢: 定時器(Timer, ScheduledExecutorService), 線程死循環+sleep
  2. 文件修改: File#lastModified

整體來說,這個實現還是比較簡單的,無論是自定義實現,還是依賴 commos-io來做,都沒太大的技術成本,但是需要注意的一點是:

  1. 千萬不要在定時任務 or 文件變動的回調方法中拋出異常!!!

為了避免上面這個情況,一個可以做的實現是借助EventBus的異步消息通知來實現,當文件變動之后,發送一個消息即可,然后在具體的重新加載文件內容的方法上,添加一個 @Subscribe注解即可,這樣既實現了解耦,也避免了異常導致的服務異常 (如果對這個實現有興趣的可以評論說明)

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://my.oschina.net/u/566591/blog/1620377

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 午夜久久久久久亚洲国产精品 | 亚欧洲乱码专区视频 | 成年人黄视频在线观看 | 国产精品久久久久网站 | 日本三级免费看 | 外女思春台湾三级 | 亚洲国产第一 | 青苹果乐园影院在线播放 | 黄a级| 午夜伦午夜伦锂电影 | 午夜精品久久久内射近拍高清 | 91精品啪在线观看国产线免费 | 精品国产美女AV久久久久 | 日本精品vide·ssex日本 | 爱情岛论坛亚洲自拍 | 国产一区二区视频在线播放 | 加勒比京东热 | 欧美三级一区二区 | 91短视频破解版 | 日本情趣视频 | 69re在线观看| 秋霞午夜伦午夜高清福利片 | 免费一级国产大片 | 4虎tv| 韩国美女豪爽一级毛片 | 日本伦理动漫在线观看 | 亚洲国产在线播放在线 | 亚洲福利二区 | 天堂网www在线中文天堂 | 色姑娘久久 | 视频在线免费看 | 日韩一区国产二区欧美三 | 欧美日韩专区国产精品 | 欧美在线播放一区二区 | 国产一级视频在线观看 | 欧美一区欧美二区 | 亚洲波霸| aaa毛片手机在线现看 | 爽好舒服使劲添高h视频 | 国产经典一区二区三区蜜芽 | 精品在线视频一区 |