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は、型の動的な拡張という同じ用途で使うことができる。※ケースに応じて好みのライブラリィを使うと幸せになれるだろう。
※Javassistのjavassist.util.proxyパッケージのjavadocを見ると"Dynamic proxy (similar to Enhancer of cglib)."とある。同じ用途で使うために用意された訳で、似ていて当たり前か。