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

Meta-Annotations について

Sato Taichi
yak shaver

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

結局何がしたいのか

自前の 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 {}

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

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を宣言すると、そこを起点に集約処理が為される。

CDI

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

Spring4

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

TomEE の中の人のアイディア

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

Bean Validation

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

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