ダイナミックプロキシを使ってみる
あるフレームワークを使って構築している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
わぉ!
これは使えそう。これでテストクラスの作成が進みそう。