Javassistとcglibにおけるメソッドフィルタの指定

既に書いたが、Javassist、cglibは共にバイトコードを出力/書き換えることにより、既存の型を動的に拡張した型を生成することができるが、この機能を利用するために提供されるプロキシ(エンハンサ)は、捕捉するメソッドをフィルタリングすることが可能だ。
捕捉するメソッドをフィルタリングする用途は使う側次第だが、意図的に捕捉対象から外したい処理がある場合以外、基本的には不要なオーバヘッドを回避することが目的になるだろう。

  • JavassistによるHogeクラスを拡張するProxyFactoryのフィルタとハンドラの指定
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Hoge.class);
factory.setFilter(new MethodFilter(){
    @Override
    public boolean isHandled(Method method) {
        //プロキシの捕捉の対象にしたくないメソッドはfalseを返すようにする
        //この例ではequalsメソッドを対象外としている
        return !(method != null && method.getName().equals("equals")
                && method.getReturnType() == boolean.class
                && method.getParameterTypes().length == 1
                && method.getParameterTypes()[0] == Object.class);
     }});
factory.setHandler(new MethodHandler(){
    @Override
    public Object invoke(Object self, Method method,
            Method proceed, Object[] args) throws Throwable {
       //MethodHandlerの無名インナークラスで捕捉対象の前後に処理を挿入できる
       System.out.println("*** before " + method.getName() + " ***");
       proceed.invoke(self, args);
       System.out.println("*** after  " + method.getName() + " ***");
       
    }});
Hoge enhancedHoge = factory.createClass().newInstance();

同様の処理をcglibで書くと、以下のようになる

  • cglibによるHogeクラスを拡張するEnhancerのフィルタとハンドラの指定
Hoge enhancedHoge = Enhancer.create(Hoge.class, null
        , new CallbackFilter(){
            @Override
            public int accept(Method method) {
                return (method != null && method.getName().equals("equals")
                        && method.getReturnType() == boolean.class
                        && method.getParameterTypes().length == 1
                        && method.getParameterTypes()[0] == Object.class)
                    ? 0 //NoOp.INSTANCEにマップする(捕捉しないということ)
                    : 1;
            }}
        , new Callback{NoOp.INSTANCE, new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method,
                    Object args, MethodProxy proxy) throws Throwable {
               System.out.println("*** before " + method.getName() + " ***");
               proxy.invoke(obj, args);
               System.out.println("*** after  " + method.getName() + " ***");
            }}}); 

JavassistのMethodHandlerに相当するインタフェースはMethodInterceptor(Callback)、MethodFilterに相当するのはCallbackFilterである。

以前にも書いたがJavassistに比べてcglibがちょっと判り難いのが、メッセージをフィルタする場合は、まずこの例のようにメッセージを捕捉するためのCallbackインタフェースを登録する際に、NoOpクラスというダミーを登録し、その後にメッセージをハンドリングするためのMethodInterceptorを登録することが必要になる。
CallbackFilterインタフェースのメソッドであるacceptは戻り値にintを必要とするのだが、ここで返す整数値は、登録されているCallbackインタフェースの配列の序数となるのである。(つまり、この例の場合acceptメソッドで0が戻ることはNoOp.INSTANCEにメソッドがマップされることなり、結果として捕捉の対象外となる)

このようにバイトコードエンハンサとしてのJavassistとcglibは、型の動的な拡張という同じ用途で使うことができる。※ケースに応じて好みのライブラリィを使うと幸せになれるだろう。

Javassistjavassist.util.proxyパッケージのjavadocを見ると"Dynamic proxy (similar to Enhancer of cglib)."とある。同じ用途で使うために用意された訳で、似ていて当たり前か。