動的プロキシとは

あるメソッドが呼ばれた際にその前後でなにかの処理を挟む手法をAOP(アスペクト指向プログラミング)といいますが、それを実現するために必要な動的プロキシという機能がJavaにあるようです。

今回は、JavaSEに標準であるjava.lang.reflect.Proxyを利用して動的プロキシを試してみました。

やること

今回は、SampleClasssomeMethod()を呼んだ際に、その前後でメッセージを出力するものを実装します。

必要なクラスやインターフェイス

SampleClass

これはsomeMethod()という抽象メソッドを定義した単純なインターフェイスになっています。

public interface SampleClass {
    void someMethod();
}

SampleClassImpl

SampleClassインターフェイスの実装をSampleClassImplクラスとして実装します。

SampleClassImplクラスには単純なメッセージを出力するだけのsomeMethod()が実装されています。

public class SampleClassImpl implements SampleClass {

    @Override
    public void someMethod() {
        System.out.println("someMethod process");
    }
}

MyInvocationHandler

MyInvocationHandlerクラスは、InvocationHandlerインターフェイスの実装をしています。

public class MyInvocationHandler implements InvocationHandler {

    Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--- Begin invoke");
        Object ret = method.invoke(target, args);
        System.out.println("--- End invoke");
        return ret;
    }
}

このクラスでは、差し込みたい処理 (と元の処理の実行)が実装されています。
差し込みたい処理とは、今回は元々の処理の前後に--- Begin invoke--- End invokeというメッセージを出力する処理になります。

InvocationHandlerではinvoke(Object proxy, Method method, Object[] args)という関数を実装する必要があって、この中でmethod.invoke(target, args)を実行することで元の処理が行われるます。つまりその前後でなにかやりたいときは、method.invoke(target, args)の前後にその処理を書けばよいということです。

ちなみにmethod.invoke(target, args)methodは元々呼ばれた関数、targetは元のクラスのインスタンス、argsは元々呼ばれた関数の引数になっています。

Main

最後はメインの処理が実装されているMainクラスです。

public class Main {

    public static void main(String[] args) {
        SampleClass sampleClass = (SampleClass) Proxy.newProxyInstance(
                SampleClass.class.getClassLoader(),
                new Class[]{ SampleClass.class },
                new MyInvocationHandler(new SampleClassImpl())
        );
        sampleClass.someMethod();
    }

}

重要なのは、Proxy.newProxyInstance()の部分です。
ここで動的プロキシを作っています。

このProxy.newProxyInstance()は、指定したインターフェイス(SampleClass)のメソッドが呼ばれた際に、その処理をMyInvocationHandlerクラス(後述)に委譲するようなインスタンスを返してくれます。

そして、動的プロキシを作った後にsampleClass.someMethod()を実行していますが、これはsomeMethod()が呼ばれるのではなく、先に説明したMyInvocationHandlerinvoke(Object proxy, Method method, Object[] args)が呼ばれるという仕組みです。

実行結果

上記のプログラムを実行すると以下のように出力されます。

--- Begin invoke
someMethod process
--- End invoke

ちゃんとsomeMethod()の実行前後に--- Begin invoke--- End invokeが出力されていますね。

処理の流れとしては以下のようになっています。

  1. Proxy.newProxyInstance()で動的プロキシのインスタンスを生成。
  2. sampleClass.someMethod()をコール(実際のSampleClass#someMethod()は呼ばれない)。
  3. MyInvocationHandler.invoke()が呼ばれる。
  4. --- Begin invokeが出力される。
  5. method.invoke(target, args)で元々の関数SampleClass#someMethod()が呼ばれてsomeMethod processが出力される。
  6. --- End invokeが出力される。

おわり

今回は、動的プロキシをJavaSEの標準機能で実装してみました。

これを覚えるといろいろできそうな気になりますが、この辺はすでにあるAOPのライブラリを使えばもっと簡単に実装できるようなので(まだよくわかってない)あとでそちらを見てみたいと思います。