実行時proxy

id:ir9Exさんのとこでメソッド呼び出し時にフックしたいというのがあったので書いてみる。

まずはInterfaceのプロキシを作るクラスの用意。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class InterfaceProxy<T> implements InvocationHandler {
    private T target;
    
    public InterfaceProxy(T target_) {
        target = target_;
    }
    
    @SuppressWarnings("unchecked")
    public Object getInstance() throws Exception {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this);
    }
    
    public Object invoke(Object self, Method method, Object[] args)
        throws Throwable {

        try {
            args = beforeInvoke(this.target, method, args);
            return method.invoke(target, args);
        } finally {
            afterInvoke(this.target, method, args);
        }
    }
    
    protected Object[] beforeInvoke(T self, Method method, Object[] args)
        throws Throwable {
        return args;
    }
    
    protected void afterInvoke(T self, Method method, Object[] args)
        throws Throwable {
    }
}

つづいてテスト対象のクラスとインタフェース。

public interface TargetInterface {
    public void Invoke1();
    public void Invoke2();
    public void Invoke3(Integer i);
}

public class Target implements TargetInterface {
    private int count = 0;
    
    public void setCount(int count_) {
        count = count_;
    }
    
    public void Invoke1() {
        System.out.println("call Invoke1(), count = " + ++count);
    }
    
    public void Invoke2() {
        System.out.println("call Invoke2(), count = " + ++count);
    }
    
    public void Invoke3(Integer i) {
        System.out.println("call invoke3(" + i + "), count = " + ++count);
    }
}

そしてテストプログラム本体。

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        try {
            Target target = new Target();
            target.setCount(10);

            TargetInterface targetProxy =
                (TargetInterface) (new InterfaceProxy<Target>(target) {

                @Override
                protected Object[]
                    beforeInvoke(Target self, Method method, Object[] args)
                throws Throwable {
                    System.out.println("before invoke");
                    if (method.getName().equals("Invoke3")) {
                        assert args.length == 1;
                        args[0] = new Integer(100);
                    }
                    return args;
                }

                @Override
                protected void
                    afterInvoke(Target self, Method method, Object[] args)
                throws Throwable {
                    System.out.println("after invoke");
                }
            }).getInstance();
            
            targetProxy.Invoke1();
            targetProxy.Invoke2();
            target.Invoke3(1);
            targetProxy.Invoke3(1);

        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}


そいで実行してみる。

before invoke
call Invoke1(), count = 11
after invoke
before invoke
call Invoke2(), count = 12
after invoke
call invoke3(1), count = 13
before invoke
call invoke3(100), count = 14
after invoke

こんな感じでProxyでちゃんとメソッドをトラップできました。
はじめてテストとして組んでみたんですが、java.lang.reflect.ProxyってInterfaceの実装クラスしかProxyとして作れないみたいなんですよね。
Hibernateとかだとinterface実装がないクラスでもproxy生成しているみたいなんで別の方法使ってるみたいです。
動的にサブクラス作ったりしてるんだろうか?


久しぶりにjavaコード書きましたがやっぱりC++erにはJava Genericsは使えねぇって感じです。
T.classとかかけない時点でもうね。new T();とかもできないし・・・
C#だとwhere句でデフォルトコンストラクタ持ってるとか制限かけられるのでもう少しつかえたんですが・・・