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)を変更したらどこかに悪影響を及ぼすかな?