アノテーションとはTigerから導入された、クラスやメソッドに注釈(Annotation)を付与することができる機構です。通常のコメント等とは異なり、アノテーションの記述はプログラムで解析したり、その内容を見てプログラムがなにか処理を実行したりすることができます。
Tigerに標準で入ってるのはたとえば Override とか Deprecated ですね。たとえばオーバライドされてるメソッドに
@Override public void hoge(){ }
などと書くことで、スーパクラスのメソッドをオーバライドしたよということを明示することができます。「オーバライドしたよ」ということを注釈してるわけですね。
Eclipseなどはたとえば、下記のようにタイプミスをした場合、
@Override public void hhoge(){ <-hogeをhhogeとしちゃった }
スーパクラスにはこのメソッドhhogeがないので、それを検知してエラーを通知してくれます。
さて自分でアノテーションを定義する方法についてですが、まず先のTigerのOverrideアノテーションを見てみます。アノテーションはふつうのインタフェース定義のようですが、下記のように@をつけて定義をするのが特徴です。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
Overrideアノテーションの定義自体にも、アノテーションが記述されてるのが分かります。先の注釈の考え方でいくと
@Target(ElementType.METHOD)
は「このOverride注釈を書くことができるのは、メソッドに対してだけです」という注釈だし
@Retention(RetentionPolicy.SOURCE)
は「このOverride注釈はソースコード上だけで、クラスファイルには記録されない」という注釈になります。
実際に作ってみましょう。たとえば、ビジネスロジック(以下、サービスと呼びます)がたくさんあって、それを生成するファクトリがあったとします。そのファクトリは
public class ServiceFactory implements IServiceFactory { public <T> T createService(Class<T> type) { なんか、生成処理。 } }
とインタフェースの型を渡すと、その型の実装クラスを返す、なんてメソッドが定義されてたとします。
このファクトリに対して、インタフェース名、実装クラス名などの注釈をつけてみます。たとえばこんな感じです。
@Services( { @Service(type = IMessageService.class, value = MessageService.class), @Service(type = ILogger.class, value = ApplicationLogger.class, singleton = true) }) public class ServiceFactory implements IServiceFactory { public <T> T createService(Class<T> type) { Annotationをみて、なんか処理。 } }
こんな感じにファクトリにアノテーションで、設定を記述することができるわけですね。
この2つのアノテーションは以下のように定義しています。
@Inherited @Target(ElementType.TYPE) // クラスにのみ @Retention(RetentionPolicy.RUNTIME) // プログラムからアクセスしたい。 @Documented public @interface Service { Class<?> type(); Class<?> value(); boolean singleton() default false; }
@Inherited @Target(ElementType.TYPE) // クラスにのみ @Retention(RetentionPolicy.RUNTIME) // プログラムからアクセスしたい。 @Documented public @interface Services { Service[] value(); }
Serviceアノテーションは、
@Service(type = IMessageService.class, value = MessageService.class)
このように設定値type,valueを持ちますが、これらはアノテーションの定義の
Class<?> type(); Class<?> value();
に対応しています。戻り値が、記述可能な型を定義しています。
また
@Service(type = ILogger.class, value = ApplicationLogger.class, singleton = true)
のsingletonという注釈は、先の記述のように記述を省略することもできます。この注釈は
boolean singleton() default false;
に対応しています。型はboolean で、設定しないとデフォルト値でそれはfalseですということですね。
次にServicesですが、
@Services({@Service(... ), @Service(... )})
のように配列のような記述をしています。これは
public @interface Services { Service[] value(); }
に対応しています。さらにvalueという変数は特殊で、一つの場合は省略可能という変数です。省略せずに書くと、
@Services(value = {@Service(... ), @Service(... )})
となるわけですが、valueだけなら冗長なので書かなくてもいいよって事になっています。それぞれのアノテーションに対しても
@Target(ElementType.TYPE) // クラスのみ
などと注釈がされてますが、これもvalueの省略形の記述というになりますね。
まとめ中。
さて先述のようにファクトリに対してアノテーションを記述しましたが、実際にプログラムからそれらの注釈にアクセスしてみます。といっても通常の変数にアクセスするのとあまり変わりません。以下のように処理をおこなう事が可能です。
public <T> T createService(Class<T> type) { // アノテーションを取得する。 Services servicess = this.getClass().getAnnotation(Services.class); if (servicess == null) { return null; } // ServicesからServiceの配列を取得。さっきのメソッド名でアクセスできる Service[] services = servicess.value(); for (Service service : services) { Class<?> typee = service.type(); Class<?> clazz = service.value(); if (service.singleton()) { // キャッシュするとか } } return null; }
このようにプログラム内からアノテーションの記述にアクセスすることができます。
この記事は
現在のアクセス:9173