既に書いたが、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)."とある。同じ用途で使うために用意された訳で、似ていて当たり前か。