XmlS2ContainerBuilderにGroovyClassLoaderを渡す
Seasarでコンポーネントにgroovyで定義したインスタンスを使いたい場合はS2GroovySeasarを使えばできますが、diconファイルを使ってもできそうです。S2ContainerBuilderのbuildメソッドにClassLoaderを引数にとるものがあるのでここにGroovyClassLoaderを渡してみます。
person.dicon : 通常のdiconファイルです。コンポーネントにnt.groovy.Personというクラス指定していますがこれは下のエントリで使ったgroovyスクリプトのクラスです。
"taedium" new java.net.URL("http://d.hatena.ne.jp/taedium/")
Client.groovy : XmlS2ContainerBuilder#build(String, ClassLoader)の引数にdaiconファイルのpathとGroovyClassLoaderを渡しています。containerからgoovyのクラスを受け取ってメソッドを実行しています。
package nt.groovy import org.seasar.framework.container.factory.XmlS2ContainerBuilder; class Client { private static final PATH = "nt/groovy/person.dicon" static void main(args) { builder = new XmlS2ContainerBuilder(); container = builder.build(PATH, new GroovyClassLoader()); person = container.getComponent("person"); println person.speak() } }
ここでClient.groovyを実行してみると、実はorg.seasar.framework.util.ClassUtil.forName(String)でClassNotFoundExceptionが起きてしまいます。JavaDocによるとClass.forName("Foo")はClass.forName("Foo", true, this.getClass().getClassLoader())と同じらしいです。Classクラスと同じクラスローダーでgroovyスクリプトをロードしようとするためクラスが見つからないのです。2ContainerBuilderに渡したGroovyClassLoaderを使うためにClassUtil.forName(String)をちょっと変更してみました。
ClassUtilの変更
public static Class forName(String className) throws ClassNotFoundRuntimeException { ClassLoader loader = Thread.currentThread().getContextClassLoader(); try { return Class.forName(className, true, loader); } catch (ClassNotFoundException ex) { throw new ClassNotFoundRuntimeException(ex); } }
Client.groovyを実行する。
結果
My name is taedium. My diary's ulr is 'http://d.hatena.ne.jp/taedium/'.
上の例ではコンテナを使う側(Client.groovy)をgroovyで書きましたがJavaでもOKですが、Javaで扱うにはgroovyのクラスがinterfaceを実装していないと扱いにくいと思います。ただ、interfaceをつかっておけば、当然実装をJavaにしたりgroovyにしたりできて、さらに同じ名前でクラス作っておけばdiconファイルも直す必要なしです。xxx.javaとxxx.groovyでソースファイルはかぶらないし使えるかも?groovyとclassファイルで全く同じ名前のクラスがあった場合はclassファイルが優先されるようです。
ClassUtil.forName(String)を変更したらどこかに悪影響を及ぼすかな?