«前の日記(2005-06-20(Mon)) 最新 次の日記(2005-06-22(Wed))» 編集
RSS feed
Webサイトとは「つい、うっかりの存在論」である

角谷HTML化計画

「むずかしく考えることはない」と、偉そうに葉巻を振りまわしながら、トレヴィラヌスはいった。「ガリラヤの太守がじつにみごとなサファイアを持っていることは、みんなが知っている。何者かがそれを盗むつもりで、間違ってここへ入ったんだ。ヤルモリンスキーが起きていたので、泥棒は殺さざるをえなかった。どうだね、これで?」
「そのとおりかもしれません。しかし、おもしろくはないですね」と、レンロットは答えた。
J.L.ボルヘス『死とコンパス』(『伝奇集』収録)

2005-06-21(Tue) [長年日記]

■1 QuickJUnitプラグイン: 3.1対応、救援求ム

quick-junit-develは35人しかsubscribeしてないので(!)、こっちにも書いておこう……。 Eclipse3.1系プラグインを触っている方の助けを求めています。

Eclipse 3.1M7で、「PDE enforces code accessibility」というのが導入されたそうで、おかげでinternal系のクラスが軒並、参照できなくなってる模様。

プリファレンス系のAPIも大きく変わっちゃってる……。振り切られました。あうあう。

私が3.1系のプラグインのつくり方を勉強して直していると、3.1リリース時点での対応は間に合わないこと確実。なので、3.1系のプラグインのことがわかる方、アドバイスをいただけないでしょうか。メーリングリストでお待ちしております。

■2 車輪の再実装: 簡易DIコンテナ on Groovy with AOP

前回、39行でDI機能を実装したGroovyのダイコン。今度はTraceInterceptorが必要になったので、実装してみた。AOP(というかInterceptor)の実装のことはよくわからないのだけれど、TDDでやったら動いちゃった。TDDすごいなあ。コンテナ部分のコードは45行増えて、84行になった。

利用イメージ

たとえば、こんなクラスをDIしてTraceInterceptしたいとする。

class TraceStub {
  doSomethingWith(arg1, arg2) {
    "doneSomething"
  }

  void throwException() {
    throw new RuntimeException("expected")
  }
}

コンテナに登録するときはBlockInjectionだよね、フツウ。

dicon = new DIContainer()
dicon.regist("myComp") { new TraceStub() }

登録したmyCompにinterceptorを適用したいときは:

dicon.intercept("myComp").with { TraceInterceptor.class }

こうでなくっちゃ。で、

dicon.myComp.doSomethingWith("someArg", "otherArg")

って呼んだら:

BEGIN com.kakutani.gradicula.TraceStub#doSomethingWith('someArg', 'otherArg')
END   com.kakutani.gradicula.TraceStub#doSomethingWith('someArg', 'otherArg') : doneSomething

と出て欲しいし、

dicon.myComp.throwException()

って呼んだら:

BEGIN com.kakutani.gradicula.TraceStub#throwException()
END   com.kakutani.gradicula.TraceStub#throwException() Throwable: java.lang.RuntimeException: expected

と出て欲しい。

DIContainer.groovy

45行も増えてしまった。なんでこんな設計になっているのか自分でもよくわかってない。テストを通るように実装したらこうなっちゃった。たぶんいろいろマズいと思うけど、自分たちが使うには充分だ。

1  package com.kakutani.gradicula
2
3  class MethodInvocation {
4    target
5    methodName
6    args
7
8    proceed() {
9      target.invokeMethod(methodName, args)
10   }
11 }
12
13 class InterceptProxy {
14   target
15   proc
16
17   InterceptProxy with(closure) {
18     proc = closure
19     this
20   }
21
22   invokeMethod(String name, Object args) {
23     try {
24       return metaClass.invokeMethod(this, name, args)
25     } catch(MissingMethodException e) {
26       interceptor = proc.call()
27       if (interceptor.class == Class.class) {
28         interceptor = interceptor.newInstance()
29       }
30       m = new MethodInvocation(
31             target:target,
32             methodName:name,
33             args:args)
34       return interceptor.invoke(m)
35     }
36   }
37 }
38
39 class DIContainer {
40   instances
41   services
42   interceptors
43
44   DIContainer() {
45     instances = [:]
46     services = [:]
47     interceptors = [:]
48   }
49
50   DIContainer regist(name, closure) {
51     services[name] = closure
52     this
53   }
54
55   InterceptProxy intercept(name) {
56     if (interceptors[name] != null) return interceptors[name]
57     proxy = new InterceptProxy(target:instance(name))
58     instances[name] = proxy
59     interceptors[name] = proxy
60   }
61
62   Object instance(name) {
63     if (instances[name] != null) return instances[name]
64     closure = services[name]
65     component = closure.call(this)
66     instances[name] = component
67   }
68
69   getProperty(String name) {
70     try {
71       return metaClass.getProperty(this, name)
72     } catch(MissingPropertyException e) {
73       return instance(name)
74     }
75   }
76
77   invokeMethod(String name, Object args) {
78     try {
79       return metaClass.invokeMethod(this, name, args)
80     } catch(MissingMethodException e) {
81       return instance(name)
82     }
83   }
84 }

TraceInterceptor.groovy

S2のTraceInterceptorの丸パクリにインスパイアされました。出力はロギングAPIじゃなくてprintlnで手抜き。

1  package com.kakutani.gradicula
2
3  class TraceInterceptor {
4    Object invoke(invocation) {
5      buf = renderSignature(invocation)
6      println("BEGIN " + buf)
7      try {
8        result = invocation.proceed()
9        buf << " : ${result}"
10       result
11     } catch (Throwable t) {
12       buf << " Throwable: ${t}"
13       throw t
14     } finally {
15       println("END   " + buf)
16     }
17   }
18
19   private StringBuffer renderSignature(invocation) {
20     buf = new StringBuffer(100)
21     buf << "${invocation.target.class.name}#"
22     buf << "${invocation.methodName}(${renderArgs(invocation.args)})"
23     buf
24   }
25
26   private String renderArgs(args) {
27     buf = new StringBuffer()
28     buf << args.toList().inject("") {|str, a| "'${a}', " }
29     if (0 < buf.length()) buf.setLength(buf.length() - 2);
30     buf.toString()
31   }
32 }

さらに先に進むために

Interceptorの登録の仕方のバリエーションを増やす。特定のメソッドにだけinterceptするための指定とか。

本日のツッコミ(全2件) [ツッコミを入れる]
shiro (2005-06-22(Wed) 04:47)

好みの問題ですが、"register(動詞)" が "regist" と省略されてるのを見るとなんとなく落ち着かないです。和製英語「レジストする」を連想するからかもしれません。

かくたに (2005-06-22(Wed) 08:04)

ううっ。そもそものMatzダイコンもregisterなので、そうします。


«前の日記(2005-06-20(Mon)) 最新 次の日記(2005-06-22(Wed))» 編集
RSS feed