Javassistやcglibでクラスをエンハンスする場合、元の型をスーパータイプとして指定するので、拡張される前の型情報は引継がれるはずだが、アノテーションに関してはそうではないので注意が必要だ。
以下のようなアノテーションを公開しているとしよう。
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataBinding {
UpdateStrategy UpdateStrategy() default UpdateStrategy.READ_WRITE;
String ComponentName() default "";
String PropertyName() default "text";
String LabelText() default "";
Class ColumnType() default Object.class;
}
これは私がフレームワークで実際に使用している、BeansBindingを利用してJavaBeans <-> JComponent間のデータバインドを自動化するためのアノテーションだ。記述の方法は、以下のようにJavaBeansのゲッター又はセッターに行う(ゲッター/セッターのどちらか一方)
public class HogeBean {
protected String fullName;
public HogeBean() {
super();
}
public HogeBean(String fullName) {
this();
this.fullName = fullName;
}
@DataBinding(ComponentName="fullName", LabelText="名前")
public String getFullName() {
return this.fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
メタアノテーション@RetentionにRetentionPolicy.RUNTIMEを指定しているので、情報はコンパイル時に保存され、ランタイム時に参照が可能だ。アノテーションはAnnotatedElementを実装するクラスからアクセスすることが可能だが、実行時にこのDataBindingアノテーションの記述の有無を調べるには、以下のようにMethod#isAnnotationPresentメソッドを使えばよい。(ClassやFieldから取得する際も同様だ)
Method method = HogeBean.class.getMethod("getFullName", null);
System.out.println( "DataBinding annotation present : "
+ method.isAnnotationPresent(DataBinding.class));
:
実行結果
:
DataBinding annotation present : true
これは全く問題無いだろう。
次は同様にアノテーションが記述されたHogeBeanクラスをJavassistで拡張した型からメソッドのアノテーション情報を取得してみよう。スーパークラスにHogeBeanを指定しているのだから、型情報と共にアノテーションも引継がれて、上と同様の結果になると期待したのだが、
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(HogeBean.class);
Class enhancedClass = factory.createClass();
Method method = enhancedClass.getMethod("getFullName", null);
System.out.println( "DataBinding annotation present : "
+ method.isAnnotationPresent(DataBinding.class));
:
実行結果
:
DataBinding annotation present : falseと実際にはアノテーションは記述されていないという検査結果になる。
AOP loses non-spring annotations in proxied class - Spring Framework Support Forums
この辺を見るとcglibでも同じ状況のようだ。
バイトコードエンハンサのポピュラな用途としてはAOPによる実行アドバイスの編み込みがあるが、その際に拡張した型をオンザフライで生成するタイミングで、元の型のオブジェクトを雛形として必要とするケースとしないケースがある。前者の雛形を必要とするケースでは元の型情報を完璧に再現できるが、余計なオブジェクトを作るため、リソースの無駄遣いになってしまう。従って後者の拡張された型だけを使う方法がベターなのだが、既に書いた通り元の型情報が欠落するので、実際にはそうもいかないのが現実だ。
今回の問題を回避するには、
- 元の型の雛形オブジェクトを生成して、必要に応じて使う
- 元の型情報をどこかに退避しておいて、拡張された型から必要に応じて参照する
- なんらかの方法で、元の型のアノテーション情報を拡張した型にコピーする
これらの方法別途必要になるだろう。
例えばJavassistであれば、javassist.bytecode.MethodInfoとjavassist.bytecode.AnnotationsAttributeを使うことによって、型に新たにアノテーションを付加できるらしいが、それには低レベルのクラス(Ct〜クラス)を扱う必要があるので、ProxyFactoryによる手軽な型の拡張が使えなくなってしまう可能性がある。(ProxyFactoryを自分で拡張してしまうってのも良いかもしれない)