単純に委譲しないメソッドのみを定義したアレをProxyクラスで何とかする
id:S_a_k_uとの話の中で、「DI的な感じで委譲させるのはいいけど委譲のコード書くのがやたら面倒だよね」という論点において合意に至り、Proxyで何とかなりそうだよねという話になったので、何とかしてみた。
コメントとか書いてません。悪しからず。
test
TDDの練習ということで、テストケースから。
import static org.junit.Assert.*; import org.junit.Test; public class XTest { final A a = new A(); @Test public void test() { X b = new B(a); assertEquals("A", b.name()); assertEquals(21, b.age()); X proxy = XProxy.create(X.class, a, new C(a)); assertEquals("A", proxy.name()); assertEquals(22, proxy.age()); } @Test(expected=XException.class) public void testThrowingException() throws Exception { X d = XProxy.create(X.class, a, new D()); d.exception(); } } interface X { public String name(); public int age(); public void exception() throws XException; } class XException extends Exception { private static final long serialVersionUID = 1L; } class A implements X { public String name() {return "A";} public int age() {return 20;} public void exception() {}; } class B implements X { private X x; public B(X x) {this.x = x;} public String name() {return x.name();} public int age() {return x.age() + 1;} public void exception() {}; } class C { private X x; public C(X x) {this.x = x;} public int age() {return x.age() + 2;} } class D { public void exception() throws XException {throw new XException();}; }
何とかするオブジェクトの生成器
import java.lang.reflect.*; public class XProxy { @SuppressWarnings("unchecked") public static <T> T create(Class<T> mockClass, InvocationHandler handler) { Class<?> proxyClass = Proxy.getProxyClass(mockClass.getClassLoader(), mockClass); try { return (T)proxyClass.getConstructor(InvocationHandler.class).newInstance(handler); } catch (Exception e) { throw new RuntimeException(e); } } public static <T> T create(Class<T> mockClass, final T x, final Object forwardable) { return XProxy.create(mockClass, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Class<?>[] parameterTypes; if (args != null) { parameterTypes = new Class<?>[args.length]; for (int i = 0; i < args.length; i++) parameterTypes[i] = args.getClass(); } else { parameterTypes = null; } return forwardable.getClass().getMethod(method.getName(), parameterTypes).invoke(forwardable, args); } catch (InvocationTargetException e) { throw e.getCause(); } catch (NoSuchMethodException e) { return method.invoke(x, args); } } }); } }
FIXME
- create(Class
mockClass, final T x, final Object forwardable)で、NoSuchMethodExceptionが起きまくる件について、forwardableのメソッドリストをあらかじめ生成しておいて、比較するとか。 - ってか、"boolean hasMethod"メソッド、なんでないんだろ…。