読者です 読者をやめる 読者になる 読者になる

AOPアスペクト指向プログラミング)といって、多くの業務機能で共通する同じ処理を「アスペクト(横断的関心ごと)」として切り出してモジュール化しようという考え方がある。

これだけだとサブルーチンと同じだが、サブルーチンの場合各機能の中で明示的にサブルーチン呼び出しの処理を書かなければならない。AOPではそれすらせずに共通処理を埋め込みたいのだ。

アスペクトモジュールへの依存が残ると、万が一引数を変えなければなんてことがあったときに、全ての機能に変更が必要になる。そうでなくても、業務機能だけ外注に出そうなんてときにちゃんとアスペクトの呼び出しを書いているかもチェック・試験が必要になり、地味に大変だ。

今回はリフレクションのお仲間のProxyクラスを使って、業務機能側には呼び出し処理を一切書かずにアスペクトの処理を差し込んでAOPを実現してみる。

元のつくりにもよるが、これによって現行の業務機能に手を加えず、アスペクトの処理を差し込むことが出来る場合もある。

業務処理インタフェース

package aopTest;

public interface Yobidasee {
    public void yobidaseeOne();
}
業務処理クラス

package aopTest;

public class YobidaseeImpl implements Yobidasee{
    public void yobidaseeOne(){
        System.out.println("------------");
        System.out.println("呼び出され1");
        System.out.println("------------");
    }
}

これをFactoryでプロキシインスタンス化する。

Factory

package aopTest;

import java.lang.reflect.Proxy;

public class TestProxyFactory {
    public static Yobidasee createNewInstance(){
        Yobidasee obj = new YobidaseeImpl();
        TestHandler handler = new TestHandler(obj);
        return (Yobidasee)Proxy.newProxyInstance(Yobidasee.class.getClassLoader(),
        		obj.getClass().getInterfaces(), handler);
    }
}
実行ハンドラ

package aopTest;

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

public class TestHandler implements InvocationHandler {
    private Yobidasee yobidasee;
    public TestHandler(Yobidasee yobidasee){
        this.yobidasee = yobidasee;
    }
    public Object invoke(Object obj, Method method, Object[] args){
        System.out.println("before");
        try{
            method.invoke(yobidasee,args);
        }catch(Exception e){}
        System.out.println("after");
        return obj;
    }
}

Factoryで業務処理のインスタンスからプロキシインスタンスを生成する。
Proxy.newProxyInstanceにクラスローダとクラスオブジェクト、そして実行ハンドラを渡すことで、プロキシインスタンスをObject型で返してくれる。
実行ハンドラはjava.lang.reflect.InvocationHandlerを実装したものを作成する。invokeメソッドをオーバーライドして記述した処理がインスタンスのメソッド実行時に代わりに実行される。
invokeメソッドの第3引数はインスタンスのメソッドの引数である。
とりあえず実行ハンドラには開始ログとして"before"、終了ログとして"after"と標準出力する処理を入れた。

呼び出し側クラス

package aopTest;

public class Yobidaser {
    public static void main(String args[]){
        Yobidasee yobidasee = new YobidaseeImpl();
        yobidasee.yobidaseeOne();
        System.out.println();
        Yobidasee yobidaseeP = TestProxyFactory.createNewInstance();
        yobidaseeP.yobidaseeOne();
    }
}

直接newした場合と、Factoryで生成した場合とでそれぞれ実行してみる。

実行結果

------------
呼び出され1
------------

before
------------
呼び出され1
------------
after

プロキシインスタンスは、生成後は通常のインスタンスと同じように扱えるため、呼び出し側の業務処理はアスペクトの存在を全く意識せず処理が出来る。アスペクトの追加も、業務処理には手を加えず可能だ。
既存システムにも、Factoryへの処理追加だけでログ機能とかを新たに追加することも出来る。まあFactoryを使わず全部直newしていたらいかんともし難そうだが。