アノテーションは、宣言または型の使用箇所に付加されるメタデータです。汎用的なオブジェクトリテラルではありません。アノテーション型は特殊なインターフェースであり、そのメンバーによって許可されるメタデータを記述します。
アノテーション型の宣言 #
アノテーションのメンバーにはパラメーターも型パラメーターもなく、throws 節を宣言できません。メンバー型として指定できるのは、プリミティブ型、String、Class、列挙型、アノテーション型、またはこれらのいずれかの一次元配列に限られます。値は、コンパイル時定数、クラスリテラル、列挙定数、アノテーション、またはそれらの配列でなければなりません。null はアノテーション要素の値になりません。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Retry {
int attempts() default 3;
Class<? extends Throwable>[] retryOn() default {};
}@Target は、アノテーションを記述できる場所を制約します。該当する場所には、宣言、型の使用箇所、型パラメーター、モジュール、レコードコンポーネントなどがあります。@Target がない場合、アノテーションは任意の宣言コンテキストに記述できますが、型コンテキストには記述できません。@Retention は、コンパイラーがアノテーションを破棄するか(SOURCE)、クラスファイルに記録するか(CLASS、デフォルト)、リフレクション用に記録するか(RUNTIME)を制御します。@Inherited が影響するのは、Class に対するクエリ、クラス上のアノテーション、そしてスーパークラスチェーンに沿った継承だけです。インターフェース、メソッド、フィールド、パラメーター、型の使用箇所にあるアノテーションは継承しません。
デフォルト値はすべての使用箇所にコピーされるのではなく、アノテーション型にコンパイルされます。そのため、古いクライアントのクラスファイルを新しいアノテーション型とともに実行した場合、変更されたデフォルト値によって観測される結果が変わる可能性があります。
リフレクションとアクセス境界 #
Class、Method、Field、Constructor、Parameter、Package、Module は AnnotatedElement を実装します。getDeclared* メソッドは、その要素に直接存在するアノテーションを調べます。getDeclared ではないメソッドは、継承されたクラスアノテーションを含める場合があります。getAnnotationsByType と getDeclaredAnnotationsByType は、繰り返し可能なアノテーションのコンテナーも展開します。
Retry[] policies = operation.getAnnotationsByType(Retry.class);実行時保持によってメタデータが可視になりますが、メンバーへのアクセスが許可されたり、private 状態を安全に公開できるようになったりするわけではありません。リフレクションは、Java 言語のアクセスチェックに加え、名前付きモジュールでは強力なカプセル化チェックにも合格する必要があります。trySetAccessible は、パッケージが呼び出し元に対して開かれている場合に限り、適用可能な言語レベルのチェックを抑制できます。コマンドラインの --add-opens はデプロイメント上の判断であり、ライブラリの契約ではありません。従来の SecurityManager は削除に向けて非推奨となっているため、セキュリティ設計では、グローバルなリフレクションポリシーを前提にせず、モジュール境界、プロセス権限、入力検証、明示的な API を使用する必要があります。
検査だけを初期化メカニズムとして使用すべきではありません。リフレクションによる構築と呼び出しはクラスを初期化する可能性があり、アノテーションメンバーへのアクセスでは、リンクされた型の解決が必要になる場合があります。静的初期化では、必須の構成や外部から観測可能な副作用を発生させないでください。
繰り返し可能なアノテーション #
@Repeatable は、繰り返されるアノテーション型の配列を唯一の必須メンバーとするコンテナーアノテーションを指定します。コンテナーの保持期間は、繰り返される型と同じか、それ以上でなければならず、互換性のあるターゲットを持つ必要があります。
@Repeatable(Labels.class)
@interface Label { String value(); }
@interface Labels { Label[] value(); }
@Label("read") @Label("write")
final class Permission {}複数回出現する場合、言語は宣言を暗黙的に宣言されたコンテナーとしてモデル化します。そのため、クラスファイルのコンシューマーからは、明示的な @Labels({ ... }) 宣言と同じコンテナー形式に見えます。単一の @Label は、単一要素の明示的なコンテナーと一般にバイト単位で同一ではありません。getAnnotation(Label.class) はコンテナーを展開しませんが、getAnnotationsByType(Label.class) は展開します。
クラスファイルとプロキシインスタンス #
実行時に可視な宣言アノテーションは、RuntimeVisibleAnnotations などのクラスファイル属性に格納されます。パラメーターアノテーションと型使用アノテーションには、それぞれ個別の属性があります。コンパイラーはアノテーションの文法を検証し、要素値をエンコードします。VM はクラスファイルの構造を検証し、コアリフレクションは必要に応じて属性を解析し、その結果を AnnotatedElement を通じて公開します。
リフレクション実装は、返される実行時アノテーションを、アノテーションインターフェースを実装するオブジェクトとして表現します。OpenJDK では、一般に動的プロキシが使用されます。クライアントは、その表現を未規定のものとして扱わなければなりません。プロキシクラスや同一性ではなく、annotationType()、宣言されたメンバーメソッド、Annotation の equals、ハッシュコード、文字列に関する契約に依存してください。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)