ダイナミックプロキシを使ってみる

あるフレームワークを使って構築しているWebアプリケーションのテストクラスを作成したくて、試行錯誤している途中。

テストクラスを作成するうえでフレームワークのコアな部分の知識は必須となるから、すごく勉強になる。

目標はフレームワークに依存する部分以外は、『pure java』。


、、、でも




、、、作成途中どうしてもmockを利用しなきゃ対処できない状況に陥った。。
(自分のスキル不足かな)




でも、djUnitやeasyMockは使用したくない。
(変な意地かな)


そこで調べたら、ダイナミックプロキシが使えるかなって思って、以下のようなプチモックを提供するクラスを実装してみた。
(実際、easyMockもこれを使っているみたい)


実装内容

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class MockHandler implements InvocationHandler {
	private Object proxy;
	private Object targetObject = null;
	private Class<?> targetClass = null;
	private Map<Method, Object> returnValues = new HashMap<Method, Object>();

	/**
	 * コンストラクタ<br>
	 *
	 * @param target 起動対象クラス
	 */
	public MockHandler(Class<?> target) {
		if (target == null || !target.isInterface()) {
			throw new IllegalArgumentException("targetはinterfaceを指定してください。");
		}
		createProxy(target);
	}

	/**
	 * コンストラクタ
	 *
	 * @param target 起動対象オブジェクト
	 */
	public MockHandler(Object target) {
		if (target == null) {
			throw new IllegalArgumentException("targetを指定してください。");
		}
		targetObject = target;
		createProxy(target.getClass());
	}

	/**
	 * プロキシインスタンスの生成をします。
	 *
	 * @param target 対象クラス
	 */
	private void createProxy(Class<?> target) {
		targetClass = target;
		try {
//			Class<?> proxyClass = Proxy.getProxyClass(target.getClassLoader(),
//					new Class[] { target });
//			proxy = proxyClass.getConstructor(
//					new Class[] { InvocationHandler.class }).newInstance(
//					new Object[] { this });
			proxy = Proxy.newProxyInstance(target.getClassLoader(),
					new Class[] { target }, this);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * プロキシインスタンスを取得します。
	 *
	 * @return プロキシオブジェクト
	 **/
	public Object getProxyObject() {
		return proxy;
	}

	/**
	 * 指定したメソッドの戻り値を設定します。
	 *
	 * @param methodName 定義メソッド
	 * @param returnValue 戻り値
	 */
	public void setReturnValue(String methodName, Object returnValue) {
		Method[] methods = targetClass.getMethods();
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				returnValues.put(method, returnValue);
			}
		}
	}

	/**
	 * プロキシインスタンスでメソッド呼び出しを処理し、その結果を返します。<br>
	 * 但し、{@link mock.MockHandler#setReturnValue()}にて、
	 * 指定したメソッドに対して戻り値を設定している場合は、その結果を返却します。
	 *
	 * @see java.lang.reflect.InvocationHandler
	 * #invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
	 */
	public Object invoke(Object obj, Method method, Object[] args) throws Throwable {

		if (returnValues.containsKey(method)) {
			return returnValues.get(method);
		}

        if (targetObject != null) {
            return method.invoke(targetObject, args);
        }
        int mod = method.getModifiers();
        if (!Modifier.isAbstract(mod)) {
            return method.invoke(obj, args);
        }

        Class<?> retType = method.getReturnType();
        if (!retType.isPrimitive()) {
            return retType.newInstance();
        } else {
            if (retType.isAssignableFrom(Integer.TYPE)) {
                return new Integer(0);
            } else if (retType.isAssignableFrom(Long.TYPE)) {
                return new Long(0);
            } else if (retType.isAssignableFrom(Short.TYPE)) {
                return new Short((short)0);
            } else if (retType.isAssignableFrom(Double.TYPE)) {
                return new Double(0);
            } else if (retType.isAssignableFrom(Float.TYPE)) {
                return new Float(0);
            } else if (retType.isAssignableFrom(Boolean.TYPE)) {
                return new Boolean(false);
            } else if (retType.isAssignableFrom(Character.TYPE)) {
                return new Character(' ');
            } else if (retType.isAssignableFrom(Byte.TYPE)) {
                return new Byte((byte)0);
            } else if (retType.isAssignableFrom(Void.TYPE)) {
                return null;
            }
        }
        return null;
    }
}

使用例

    MockHandler mock = new MockHandler(List.class);
    mock.setReturnValue("size", 10);
    List list = (List) mock.getProxyObject();
    System.out.println(list.size());

実行結果

10



わぉ!
これは使えそう。これでテストクラスの作成が進みそう。