Springは、コンポーネントの検出、設定、依存性注入、トランザクション、Webリクエストマッピング、その他のフレームワークサービスにおいて、宣言的メタデータとしてアノテーションを使用します。Javaリフレクションは生のアノテーションを公開しますが、Springは合成アノテーション、エイリアス、検索戦略、クラスをロードせずにメタデータを読み取るための一貫したモデルを追加します。
Javaアノテーションのセマンティクス #
アノテーション宣言自体はインターフェースであり、暗黙のスーパーインターフェースは
java.lang.annotation.Annotationです。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Audited {
}実行時リフレクションでは、クラスに@Auditedが直接付与されている場合に検査できます。@Retention、@Target、@Documentedなどのメタアノテーションはアノテーション宣言を記述しますが、任意の推移的な検索を引き起こすわけではありません。
SOURCE、CLASS、RUNTIMEの保持ポリシーは、メタデータがソースコードのみに残るか、クラスファイルに残るか、リフレクションから利用可能になるかを決定します。@Targetは宣言または型使用の場所を制限します。反復可能アノテーションは@Repeatableによってコンテナを指定します。リフレクションとSpringは、論理的な反復アノテーションまたは物理的なコンテナのいずれかを公開できます。
@Inheritedは限定的な例外です。一部のClass検索メソッドは、アノテーション宣言に@Inheritedが付与されている場合、スーパークラス上の継承されたアノテーションを検出できます。この規則はインターフェースには適用されず、メソッドやフィールドも対象外であり、Springの合成アノテーションモデルとは別のものです。
合成アノテーション #
Springは、他のアノテーション宣言に付与されたアノテーションを解釈します。例えば、@Serviceにはメタアノテーションとして@Componentが付与されています。
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}@Serviceが付与されたクラスは、通常のリフレクションでは@Componentが直接付与されているとは報告されないにもかかわらず、結果としてコンポーネント候補になります。アプリケーションは同じ仕組みを使用して、ドメイン固有のアノテーションを作成できます。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
@Transactional(readOnly = true)
public @interface ReadOnlyService {
}これはJava言語の規則ではなく、プログラミングモデルです。Springのコードは、走査、循環検出、フィルタリング、属性の合成を実行する必要があります。
マージ済みアノテーション #
MergedAnnotationsは、直接付与、継承、メタ付与されたアノテーションに対するSpringの中心的なクエリモデルです。そのSearchStrategyは、どの型関係を走査するかを制御します。
DIRECTは、要素に直接宣言されたアノテーションのみを検査します。INHERITED_ANNOTATIONSは、Javaの@Inherited規則に従います。SUPERCLASSは、スーパークラス階層を検索します。TYPE_HIERARCHYは、スーパークラスと実装されたインターフェースを検索します。
フレームワーク機能に適合する最も狭い戦略を使用してください。広範な階層検索では、Javaリフレクションだけでは公開されないメタデータが生成される可能性があります。
MergedAnnotation<T>は、検出された1つのアノテーションを、その距離、集約位置、ソース、合成された属性とともに表します。合成処理は、@AliasForで宣言されたSpringのエイリアス関係を解決します。したがって、合成アノテーションとそのメタアノテーション全体で、一貫した属性ビューを提示できます。
エイリアス宣言は一貫していなければなりません。1つのアノテーション内では、エイリアス対象の属性が互換性のある戻り値型とデフォルト値を持つ必要があります。アノテーション間のエイリアスは、メタアノテーション階層内で到達可能な属性を識別する必要があります。
メタデータ抽象化 #
Springは、一般的なアノテーション付き要素のメタデータと、クラス固有およびメソッド固有のメタデータを分離します。
AnnotatedTypeMetadataは、型とメソッドに共通するアノテーションクエリを公開します。AnnotationMetadataは、クラス情報とアノテーション付きメソッドへのアクセスを追加します。MethodMetadataは、宣言クラス、戻り値型、メソッドプロパティを追加します。
リフレクションベースの実装は、ロード済みクラスを検査します。クラス読み取り実装はバイトコードメタデータを使用するため、コンポーネントスキャンや設定解析などのインフラストラクチャは、クラスを初期化せずに検査できます。クラスのロードを回避することで、静的初期化の副作用を防ぎ、すべてのオプション依存関係を解決できる前にメタデータを検出できます。
ロード済みクラスには、サポートされているイントロスペクションのエントリポイントを使用します。
AnnotationMetadata metadata =
AnnotationMetadata.introspect(ExampleConfiguration.class);
boolean configuration =
metadata.hasAnnotation(Configuration.class.getName());フレームワーク内部では、代わりにMetadataReaderFactoryからMetadataReaderを取得して、.classリソースを読み取ることができます。
設定クラスの処理 #
ConfigurationClassParserは、@Configuration、コンポーネントスキャン、インポート、プロパティソース、条件、@Beanメソッドを処理しながらクラスメタデータを消費します。@Importは、設定クラス、セレクター、レジストラーを追加できます。@Conditionalの実装は、該当する解析フェーズまたはBean登録フェーズで設定を拒否できます。
全体的な流れは次のとおりです。
- Bean定義の候補を検出します。
- クラスメタデータを読み取ります。
- 各候補が設定クラスかどうかを判定します。
- 条件を評価し、インポートとコンポーネントスキャンを走査します。
- Beanメソッドを検出し、結果のBean定義を登録します。
アノテーション解析は定義を記述します。その後のBeanファクトリフェーズでBeanがインスタンス化され、初期化されます。これらのフェーズを分離しておくことで、アノテーションを検出しても、付与された動作が直ちに実行されるわけではない理由を説明できます。
APIの選択 #
直接的なJavaアノテーションのセマンティクスで十分な場合は、Javaリフレクションを使用します。Springの合成アノテーションまたはエイリアスを尊重する必要があるインフラストラクチャを実装する場合は、MergedAnnotationsを使用します。コンテナ起動中にクラスレベルおよびメソッドレベルのメタデータを検査する必要がある場合、特にクラスのロードが時期尚早となる場合は、AnnotationMetadataまたはMetadataReaderを使用します。
アプリケーションコードでは、通常、アノテーション検索規則を再実装するのではなく、コンポーネントスキャン、トランザクション管理、MVCマッピングなどの高レベルなSpring機能を利用します。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)