メインコンテンツまでスキップ

Meta-Annotations について

Sato Taichi

このエントリは、アイディアや情報の提供を呼びかけるためのものです。

結局何がしたいのか#

自前の AnnotationProcessor で Meta-Annotation 的な機能をサポートしたい。

で、そもそも Meta-Annotation ってこんなんで良いんだっけ?というエントリです。

解決したい問題#

同じアノテーションの組合せを沢山のクラスに設定したくない#

アノテーションの合成#

こういう二つのアノテーションがあったとして

public @interface Transactional {}public @interface Service {}

これらを一つにまとめるアノテーションを定義したい。

@Service@Transactionalpublic @interface TransactionalService {}

で、これを使うコードと使ってないコードが同じであるとみなしたい。

@TransactionalServicepublic class MyService {}
@Service@Transactionalpublic 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 {}

こういう風に使いたい。

@RuntimeTypepublic @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 {}

つまり、MyTxServiceMySimpleTxServiceは同じように解釈されてほしい。

import static tx.Transactional.TxType.REQUIRED;
@Transactional(REQUIRED)public class MyTxService {}
@TxRequiredpublic 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@FieldAndArgspublic @interface WideTarget {}

@Targetアノテーションの value をマージして良いか不明瞭である。@Merge等のマーカを定義するのだろうか。

定義が循環するケース#

アノテーションが相互参照していると解決が終わらなくなる可能性がある。

@Borospublic @interface Ouro {}@Ouropublic @interface Boros {}

解決の階層にリミットを設けるか、循環を検知してエラーにする必要がある。

どうやって集約するのか#

既存のやり方#

Groovy#

@groovy.transform.AnnotationCollectorを宣言すると、そこを起点に集約処理が為される。

CDI#

@Stereotypeがあり一緒に宣言されているアノテーションが集約される

Spring4#

リフレクションベースの実装で、何でも合成できるようになっている。 処理コストが随分と高そうな印象。

TomEE の中の人のアイディア#

dblevins の書いたエントリでは@Metatypeが付いてるものをアノテートするとまとめられる。

Bean Validation#

BeanValidation では@OverridesAttributeを使うと値の上書きと共に、当該アノテーションに宣言されているアノテーションの継承のような事ができる。

つまり、Bean Validation では Constraint を宣言する際には合成できるよう Target に ANNOTATION_TYPE を付けることが望ましいのか。