Javaがファイルの変更をどうやって監聴できるかの例


アプリケーションでログ出力コンポーネントとしてlogbackを使うと、ほとんどのファイルをlogback.xml`に配置します。また、生産環境下で、logback.xmlファイルのログレベルを直接修正して、アプリケーションを再起動しなくても有効になります。この機能はどうやって実現されますか?
アプリケーションでログ出力コンポーネントとしてlogbackを使うと、ほとんどの場合、logback.xmlというファイルを配置します。また、生産環境では、logback.xmlファイルのログレベルを直接修正して、アプリケーションを再起動しなくても有効です。
では、この機能はどうやって実現されますか?
I.問題の説明と分析
上のこの問題に対して、まず実際のcaseを投げます。私の個人ウェブサイトZ+の中で、すべてのツールは設定ファイルを通して動的に追加したり隠したりします。サーバーは一台しかないので、配置ファイルは簡単にサーバーのカタログの下に置きます。
現在の問題は、このファイルの内容が変更された時に、アプリケーションがこのような変動を感知し、ファイルの内容を再読み込みし、アプリケーションの内部キャッシュを更新する必要があります。
一つの最も容易に思いつく方法はポーリングです。ファイルが修正されたかどうかを判断します。修正すれば、メモリを再読み込みし、メモリを更新します。
  • はどうやってポーリングしますか?
  • どのようにファイルが修正されたかを判断しますか?
  • 設定が異常で、サービスが利用できないことがありますか?つまり、これは今回のテーマとあまり関連がないですが、重要です。
  • II.設計と実現
    問題が抽象化されたら、対応の解決策がより明確になります。
  • どうやってポーリングしますか?--」タイマーTimer、ScheduledExectorServiceはすべて
  • を実現することができます。
  • 文書の修正はどう判断しますか?--」java.io.File_fiedによってファイルの最終修正時間を取得します。照合すればいいです。
    簡単な実現がしやすいです。
    
    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();
        }
      }
    }
    
    
    上のこれは非常に簡単で、非常に基礎的な実現に属しています。基本的には私達の需要を満たすことができます。この実現には何か問題がありますか?
    タイミングタスクの実行中に異常が発生したらどうなりますか?
    上のコードを少し修正します。
    
    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();
        }
      }
    }
    
    
    実際のテストでは、最初の修正の時だけ、上記のコードがトリガされましたが、再度修正すると効果がなくなりました。すなわち、例外が出たら、タイミングタスクはもう実行されなくなります。この問題の主な原因はSchduledExectorServiceのためです。
    SchduledExectorServiceのソースコメントを直接確認します。
    If any execution of the task encounters an exception、subsequent executions are suppresed.Otherwise、the task will onely terminate viation or termination of the exector.つまり、タイミングタスク実行中に異常が発生した場合、後のタスクは実行されません。
    ですから、この姿勢を使う時は、自分の任務に異常がないようにしてください。そうしないと、後は遊べません。
    対応の解決方法も比較的簡単です。全体的にスイッチを入れるといいです。
    III.階段版
    もちろん、javaサークルでは、基本的に多くの一般的な需要があります。対応するオープンソースツールを見つけて使用することができます。もちろん、これも例外ではありません。また、皆さんの属性を比較するapacheシリーズです。
    まずマven依存
    
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>
    主にこのツールの中のFileAlteration Observer、FileAlteration Listener、FileAlteration Monitorの3つの種類を使って関連の需要シーンを実現しました。もちろん使うのも簡単です。これからはどのように説明できますか?直接に見てください。私のオープンソースプロジェクト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;
        }
      }
    
    
      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);
        }
      }
    }
    
    
    上記の実現について、簡単にいくつか説明します。
  • このファイルの傍受は、ディレクトリを元に、フィルタを設置してファイルの変動に対応する傍受を実現することができます。
  • は上のようにregister ConfChange Listener方法で、着信したfileは具体的な配置ファイルであるため、パラメータを構築する際にディレクトリを取り出し、ファイル名をフィルタ
  • として取得した。
  • の第2のパラメータはjdk 8シンタックスであり、具体的にはプロファイルの内容を読み取り、対応するエンティティオブジェクト
  • にマッピングする。
    一つの問題は、funcメソッドを実行する時にも異常を投げたらどうなりますか?
    実際のテストの結果は上と同じです。異常を投げた後も、まだひざまずいていますので、異常を出さないように注意してください。
    簡単に上の実現ロジックを見て、コアモジュールを直接差し引きます。
    
    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バージョンを追加します
    jdk 1.7は、Watch Serviceを提供しています。ファイルの変更を実現するための監聴もできます。これに接触したことがないので、このものがあることを知っています。そして、使用に関して調べてみました。簡単な説明もあります。イベント駆動式によるもので、効率が高いということです。以下にも簡単な例を示します。
    
    @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を使って配置ファイルの変更の傍受を実現します。主に2つの点に関連しています。
  • どうやってポーリングしますか?タイマー(Timer、ScheduledExectorService)、スレッドのデッドサイクル+slep
  • ファイルの修正:File Modified
  • 全体としては、この実現は比較的簡単で、カスタマイズの実現にも、comos-ioに頼るにもあまり大きな技術コストではないが、注意すべき点は:
  • 千万円はタイミングタスクorファイルの変動のコールバック方法に異常を投げないでください!!
  • 上記のような状況を回避するために、EventBusの非同期メッセージ通知によって実現され、ファイルが変更された後にメッセージを送信すればよく、ファイルの内容を具体的に再ロードする方法で@Subscribe注釈を追加すれば良い。異常によるサービス異常も避けられました。
    以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。