Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

JDaveでテストクラスにDIする

Springと連携したサンプルがJDaveにあって(といってもJavadocコメントに直書きだけど)。
こんな感じ。

public class MySpecification extends Specification<Something> {
    private SomeInjectedField field;
    
    public MySpecification() {
        new InjectionSupport().inject(this, new SpringInjector());
    }
    
    public class SomeContext {
        ...
    }
}

public class SpringInjector implements IFieldInjector {
    private BeanFactory factory = new ClassPathXmlApplicationContext("/myAppContext.xml");

    public void inject(Field field) {
        if (factory.containsBean(field.field().getName())) {
            field.set(factory.getBean(field.field().getName()));
        }
    }
}

テストクラスのコンストラクタで、自分自身とIFieldInjectorを引数にInjectionSupport#inject()メソッドを呼び出す。
IFieldInjectorにはinject()メソッドだけが定義されてる。
で、DIはSpringの設定ファイルからファクトリを作って、テストクラスのフィールド名をBean名とみなしてコンテナから取り出し、セットしてるだけ。

public interface IFieldInjector {
    void inject(Field field);
}

このFieldクラスはjava.lang.reflect.FieldをラップしたJDaveのFieldクラス。

InjectionSupportはこんなの。

public class InjectionSupport {
    public void inject(Object object, IFieldInjector injector) {
        Class<?> type = object.getClass();
        do {
            List<Field> fields = fieldsFor(object, type);
            for (Field field : fields) {
                injector.inject(field);
            }
        } while ((type = type.getSuperclass()) != null);
    }
}

InjectionSupportクラスは引数で渡されたクラスのフィールドを取り出して、IFieldInjector#inject()メソッドに渡してる。

Seasar2版のIFieldInjectorを作ってみた

package jyukutyo.sample;

import jdave.injection.Field;
import jdave.injection.IFieldInjector;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class Seasar2Injector implements IFieldInjector {

	private S2Container container;
	
	public Seasar2Injector() {
		S2ContainerFactory.configure("test.dicon");
		container = S2ContainerFactory.getConfigurationContainer();
	}

	public void inject(Field field) {

		String fieldName = field.field().getName();

		if (container.hasComponentDef(fieldName)) {
			field.set(container.getComponent(fieldName));
		}
	}
}

Springと同じように設定ファイルを読み込んで、フィールド名をコンポーネント名とみなしてセットするだけ。
設定ファイルはコンポーネントの自動登録を利用した。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
  "http://www.seasar.org/dtd/components24.dtd">
<components>
	<component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister">
		<property name="instanceDef">
			@org.seasar.framework.container.deployer.InstanceDefFactory@SINGLETON
		</property>
		<property name="autoNaming">
			<component class="org.seasar.framework.container.autoregister.DefaultAutoNaming"/>
		</property>
	    <initMethod name="addClassPattern">
	        <arg>"jyukutyo.sample"</arg>
	        <arg>".*Impl"</arg>
	    </initMethod>
	    <initMethod name="registerAll" />
	</component>
</components>

ポイントは「」をinitMethodで呼び出すこと。
AbstractAutoRegister#registerAll()はWebアプリケーションだったらデプロイ時にSeasar2が呼び出してくれてるのかな。。。
ちょっとどこだかわからかったけど、テストでは自分で呼び出しておく必要がある。
InstanceDefFactoryは今回ステートレスなオブジェクトをDIするでSINGLETONにしてるけど、実際は普通PROTOTYPEだろう。
でもPROTOTYPEだと毎回生成するから、テストが多くなったらコストがかかるかな。。。?
このへんテストのコンテキストでDIするインスタンスの生成について考えないといけないかも。


今はフィールド名からコンポーネントを取得してるけど、せっかくSeasar2だしAutoNamingの規約を使えば、DIするクラス(インタフェース)の型からうまくやれるかも。