2009年12月18日金曜日

AspectJ で using 的な構文を模倣してみる (2)

前回のポストで試した using ぽい書法を、改善してみる。 前に書いた叩き台では、リソースクラスに解放用のインターフェイス Disposable を実装させていたが、任意のクラスに対応できるように抽象化する。こんなコードにしてみた。
public abstract aspect UsingAspect<T, U> {
  protected T obj;

  pointcut settingField(T o): set (T+ U+.*) && args(o);
  pointcut using(): call(U+.new(..));

  after(T o): settingField(o) {
    this.obj = o;
  }
  after(): using() {
    endUsing(this.obj);
  }

  protected abstract void endUsing(T o);
}
この Generic な抽象アスペクトを、それぞれのリソース型について以下のように具体的する。
  • 前回の Disposable オブジェクトの例。
    public interface UsingDisposable {
      public static aspect DisposableAspect
          extends UsingAspect<Disposable, UsingDisposable> {
        @Override
        public void endUsing(Disposable o) {
          if (null != o)((Disposable)o).dispose();
        }
      }
    }
  • java.io の Writer に適用する例
    interface UsingWriter {
      public static aspect UsingWriterAspect 
          extends UsingAspect<Writer, UsingWriter> {
        @Override
        protected void endUsing(Writer o) {
          try { if (null != o) o.close(); } 
          catch (IOException ex) { throw new RuntimeException(ex); }
        }
      }
    }
リソースオブジェクトごとに違うのは、つまるところオブジェクトの型と解放方法なので、カスタマイズするのはその部分だけに留めたい。取り敢えず一つの実装方法としてはこんな風なコードになる。( pointcut の定義で
pointcut using(): call(Using<T>+.new(..));
みたいなパラメタライズした書き方ができれば、更に堅牢で経済的なコードになるけど、残念ながら文法的に許されないらしい。) クライアントコードは以下のようになる
public class Client {
  public static void main(final String[] args) throws Exception {
    new UsingWriter(){
        PrintWriter s = new PrintWriter("test.txt"); {
      s.println("one line written");
      s.flush();
    }};
  }
}
デバッガを使うとか、コンソール入力を待つ行を追加するとかして、適当な箇所で止めながら動かすと、無名クラス生成ブロックの中でだけ ファイル test.txt にロックがかかっているのが確かめられる。 あとはマルチスレッドとネストか。 } } 実行すると以下のようなコンソール出力を得る。
A("r1") used with "x"
B("r2") used with "y"
A("r3") used with "z"
A("r3") disposed
B("r2") closed
A("r1") disposed
割と簡単に using のネストができた。一応、だんだんと実用的になってきた気がする。取りあえずテストコードで使い始めて、パフォーマンスなんかを中心にしばらく観察してから、問題が無ければプロダクトコードで使ってみるような流れで良いか。

0 件のコメント:

コメントを投稿