Meta-Annotations について
このエントリは、アイディアや情報の提供を呼びかけるためのものです。
結局何がしたいのか
自前の AnnotationProcessor で Meta-Annotation 的な機能をサポートしたい。
で、そもそも Meta-Annotation ってこんなんで良いんだっけ?というエントリです。
解決したい問題
同じアノテーションの組合せを沢山のクラスに設定したくない
アノテーションの合成
こういう二つのアノテーションがあったとして
public @interface Transactional {}
public @interface Service {}
これらを一つにまとめるアノテーションを定義したい。
@Service
@Transactional
public @interface TransactionalService {}
で、これを使うコードと使ってないコードが同じであるとみなしたい。
@TransactionalService
public class MyService {}
@Service
@Transactional
public class MyService {}
値の設定を伴う合成
値を設定するケースでも同じように書きたい。以下のようなアノテーションを定義する。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeType {}
こういう風に使いたい。
@RuntimeType
public @interface MyTypeAnnotation {}
@RuntimeType
を使った宣言は、以下の宣言と同じであるとみなしたい。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTypeAnnotation {}
ここでの重要なポイントは、@Target
や@Retention
ではデフォルト値が存在しないが、@RuntimeType
宣言時に必要な値を埋め込んでいることである。
汎用的な定義を具体的な定義として読み替える
以下のような使い方も想定できる。
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Target;
@Target({ TYPE, METHOD })
public @interface Transactional {
TxType value();
enum TxType {
REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER;
}
}
@Transactional
はデフォルト値の無い汎用的な宣言だが、値の設定を伴わない具体的な宣言に定義しなおしたい。
import static tx.Transactional.TxType.REQUIRED;
@Transactional(REQUIRED)
public @interface TxRequired {}
つまり、MyTxService
とMySimpleTxService
は同じように解釈されてほしい。
import static tx.Transactional.TxType.REQUIRED;
@Transactional(REQUIRED)
public class MyTxService {}
@TxRequired
public class MySimpleTxService {}
値の上書きを伴う合成
public @interface Range {
long min() default Long.MIN_VALUE;
long max() default Long.MAX_VALUE;
}
@Range
に対して@Positive
という値を絞り込んだアノテーションを作る。
@Range(min = 0L)
public @interface Positive {
long max();
}
@Positive
は、max を上書きできるが、min は上書きできない。
@Range
では、max を設定しない場合、Long.MAX_VALUE
がデフォルト値として適用されるが、@Positive
では max を設定しなければならない。
以下の例では、val と pos は同じ意味をもつが、デフォルト値のある@Range
とデフォルト値の無い@Positive
ではコンパイルエラーの出力が異なる。
public class MyClass {
@Range(min = 0, max = 200)
long val;
@Positive(max = 200)
long pos;
}
発生する問題
値がコンフリクトする
@Positive
とは逆のアノテーションを作成する。
@Range(max = 0L)
public @interface Negative {
long min();
}
以下のように宣言できてしまう。
public class Conflicts {
@Positive(max = 100)
@Negative(min = -100)
long val;
}
これは、以下のように解釈されるべきだ。
public class Conflicts {
@Range(min = 0, max = 100)
@Range(min = -100, max = 0)
long val;
}
これは二つの問題がある。
@Range
は Repeatable ではないが、暗黙裡に Repeat している。- 展開した状態で評価することでコンパイルエラーとして検出すべき
- min と max の値がそれぞれ矛盾している。どの値を採用すべきか?
- 値が矛盾しているが果たしてエラーか?
値をマージして良いか不明瞭なケース
二つのアノテーションがある。
import static java.lang.annotation.ElementType.*;
@java.lang.annotation.Target({ TYPE, METHOD })
public @interface TypeAndMethod {}
import static java.lang.annotation.ElementType.*;
@java.lang.annotation.Target({ TYPE, FIELD, PARAMETER })
public @interface FieldAndArgs {}
これらを使って新しいアノテーションを作る。
@TypeAndMethod
@FieldAndArgs
public @interface WideTarget {}
@Target
アノテーションの value をマージして良いか不明瞭である。@Merge
等のマーカを定義するのだろうか。
定義が循環するケース
アノテーションが相互参照していると解決が終わらなくなる可能性がある。
@Boros
public @interface Ouro {}
@Ouro
public @interface Boros {}
解決の階層にリミットを設けるか、循環を検知してエラーにする必要がある。
どうやって集約するのか
既存のやり方
Groovy
@groovy.transform.AnnotationCollector
を宣言すると、そこを起点に集約処理が為される。
- 1.7.6. Meta-annotations
- http://blog.andresteingress.com/2013/01/25/groovy-2-1-the-annotationcollector-annotation/
- http://mrhaki.blogspot.jp/2013/02/groovy-goodness-combining-annotations.html
CDI
@Stereotype
があり一緒に宣言されているアノテーションが集約される
- http://docs.jboss.org/cdi/spec/1.1/cdi-spec.html#stereotypes
- https://docs.oracle.com/javaee/7/tutorial/cdi-adv008.htm#GKHQC
Spring4
リフレクションベースの実装で、何でも合成できるようになっている。 処理コストが随分と高そうな印象。
- http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-meta-annotations
- https://github.com/spring-projects/spring-framework/tree/master/spring-core/src/main/java/org/springframework/core/type/classreading
TomEE の中の人のアイディア
dblevins の書いたエントリでは@Metatype
が付いてるものをアノテートするとまとめられる。
- http://blog.dblevins.com/2012/11/meta-annotations.html
- https://github.com/dblevins/metatypes
- http://tomee.apache.org/examples-trunk/access-timeout-meta/README.html
Bean Validation
BeanValidation では@OverridesAttribute
を使うと値の上書きと共に、当該アノテーションに宣言されているアノテーションの継承のような事ができる。
つまり、Bean Validation では Constraint を宣言する際には合成できるよう Target に ANNOTATION_TYPE を付けることが望ましいのか。