org.apache.wicket.util.watch.ModificationWatcherの概略

はじめに

 DEVELOPMENTモードにすると、Wicketがファイルの変更監視を行うことを知っている人は多いかと思います。
 変更監視の中核となっているのが、org.apache.wicket.util.watch.ModificationWatcherクラスです。

 というわけで、その辺のソースコードをちょっと読んでみました。
 Wicketが変更監視をどのように行っているのか少し覗けました。場合によっては、DEVELOPMENTモード時の監視対象を増やしたい人の取っ掛かりになるかもしれません。

 メモの趣旨は、興味の赴くままソースを読んだのでその記録、です。

環境


ModificationWatcher周辺の概略クラス図

 以下は簡略化したクラス図です。GOFのObserverパターンを変形した物を実装しています。きっと。


クラス名 説明
ModificationWatcher 監視を実施するクラスです。IModifiableのインスタンスに変更日時を問い合わせて、前回の問い合わせ時点と変更日時に相違があれば変更があったと見なします。
IModifiable 監視対象を表すインターフェースです。監視対象がファイルの場合、1ファイルに1インスタンス必要です。
IChangeListener 監視対象に変更があった場合に、「変更がありました」とModificationWatcherから通知を受けるクラスが実装するインターフェースです。

何が監視対象?

 監視対象の調査は面倒な予感がしましたので、解析を途中で断念しました。
 監視対象は、org.apache.wicket.util.watch.IModifiableを実装するクラスで表現される「何か」です。
 監視対象を追加するorg.apache.wicket.util.watch.addメソッドの引数の型から、そのことは分かります。

 で、Eclipseを使用してIModifiableの継承関係を表示したのが下記の図です。

 それぞれのクラスが何に対応しているのか、正攻法で調べるのは諦めたくなった私の気持ちが何となく分かるかと思います。
 もっとも、IModifiableを実装していても、ModificationWatcherにaddされないものもあるかもしれませんが。

変更を検知するクラスにはどんなのがある?/どこで監視対象を登録している?

 興味深いことに、変更の検知に応答するクラスは少ないです。

 匿名クラスばかりですので、ぱっと見なんだかよく分かりませんが、数は4つです。
 そして、実は4つのうち1つは、ModificationWatcherにaddされません。

 ModificationWatcherにaddされる3つは、下記のクラス内で定義されています。で、匿名クラスの定義と同時にModificationWatcher.addを呼び出しているので、監視対象の登録箇所も同一です。

 クラスファイルの監視も、他のファイルの監視も、使用している仕組みは同じだったのですね。

余談

 ちょっと面白いのが、ReloadingClassLoaderで実装されているIChangeListenerです。
 ReloadingClassLoaderの321行目付近を引用します。

watcher.add(clzFile, new IChangeListener()
{
    public void onChange()
    {
        log.info("Class file " + finalClzFile + " has changed, reloading");
        try
        {
            listener.onChange();
        }
        catch (Exception e)
        {
            log.error("Could not notify listener", e);
            // If an error occurs when the listener is notified,
            // remove the watched object to avoid rethrowing the
            // exception at next check
            // FIXME check if class file has been deleted
            watcher.remove(finalClzFile);
        }
    }
});

 さらに別のonChangeメソッドを呼び出す実装になっています。
 内部で使用されているlistenerの実体は、ReloadingWicketFilterで実装されているIChangeListenerになっており、そちら側でクラスローダの再生成を行っています。
 なぜそうしたのかまでは分かりませんが、Chain of Responsibility パターンぽくなっていて面白いですね。

監視スレッドはいくつ起動する?

 監視対象が複数種類ありそうだと言うことは、ここまでで分かりました。
 では、ModificationWatcherのインスタンスは何個作られるのでしょう。
 Eclipseを使用してModificationWatcherのコンストラクタが呼ばれている箇所を調べたのが下記の図です。

 ソースを読むとReloadingClassLoaderクラス、Settingsクラスともに1回ずつ生成していますので、ModificationWatcherのインスタンスは最大2個です。で、インスタンス生成と同時にスレッドが起動しますのでスレッド数も2個です。

まとめ+α

  • 監視対象の更新日時に相違があった場合に「変更があった」とみなすことが前提となっている。
  • ModificationWatcherは監視だけする。変更があった場合に何をするかはIChangeListenerを実装するクラスが決める。ソースを読んでみるとIChangeListenerを実装するクラスは、キャッシュから変更のあったファイルを取り除いたり、キャッシュのクリアを行ったりしている。
  • 監視用スレッドは、「クラスファイル用」と「他のファイル用」の最大2個起動する。


最後に

 本当は、どのファイルが監視対象なのかまで特定出来ると良かったのですが、私の根性の都合で無理でした。
 わりとシンプルな仕組みなっているので、Wicketと何か別のプロダクトを併用している場合に、監視対象を増やすとか出来てしまいそうです。
 もっとも実際にやってみると、「知らない誰かがキャッシュをしていて、上手く読み直せないっ」とかあったりもするのですが。