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するための指定とか。
好みの問題ですが、"register(動詞)" が "regist" と省略されてるのを見るとなんとなく落ち着かないです。和製英語「レジストする」を連想するからかもしれません。
ううっ。そもそものMatzダイコンもregisterなので、そうします。