java.lang.reflect.Proxy

恥ずかしながら使い方を把握していなかった。

import java.util.*;
import java.lang.reflect.*;

public class TestProxy
{
  public static void main(String[] args) throws Exception
  {
    TreeSet set = new TreeSet();

    Set proxySet = (Set)Proxy.newProxyInstance(
        set.getClass().getClassLoader()
        , new Class[]{ Set.class }
        , new InvocationHandlerImpl(set) );
    
    proxySet.add("hoge");
    try
    {
      proxySet.add(new NonComparableObject());
    }
    catch(Exception e)
    {     
    }
  }
  
  public static class InvocationHandlerImpl implements InvocationHandler
  {
    private Object instance;
    
    public InvocationHandlerImpl(Object instance)
    {
      this.instance = instance;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
    {
      System.out.println("[BEFORE]" + instance.getClass().getName() + "#" + method.getName() );
      try
      {
        Object ret = method.invoke(instance, args);
        System.out.println("[AFTER]" + ret );
        return ret;
      }
      catch(InvocationTargetException e)
      {
        System.out.println("[EXCEPTON]" + e.getTargetException() );
        throw e.getTargetException();
      }
      catch(Throwable e)
      {
        System.out.println("[EXCEPTON]" + e );
        throw e;
      }
    }
  }
  
  public static class NonComparableObject
  {
  }
}

実行結果

[BEFORE]java.util.TreeSet#add
[AFTER]true
[BEFORE]java.util.TreeSet#add
[EXCEPTON]java.lang.ClassCastException: TestProxy$NonComparableObject

なるほど、インターフェース切って置く事が大前提とは言うものの、単純に公開メソッドにフックかける程度でしかAOPを使わないというのなら、これでも十分使い物になる。そしてProxyクラスのメソッド内で本来のインスタンスメソッドに委譲している関係上 *1インスタンスメソッド内でインスタンスメソッドを呼んだ場合、それはProxyクラスのメソッドではなく本来のインスタンスのメソッドになってしまい、フックがかからないという制約の理屈も理解できた。

*1:InvocationHandler#invoke の中でスタックトレースみて、Method#invoke に渡すインスタンスを切り替えるという手をふっと思いついたけれど、パフォーマンス諸々の観点でこれはどうなんだろうか……。やっぱり無理。ダメぽ。どうにもなんね