Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 45 - Reflection

45.6 Annotationen



45.6.1 Grundlagen

Zum Abschluss dieses Kapitels wollen wir uns mit der Möglichkeit beschäftigen, Informationen im Quelltext abzulegen, die von Hilfsprogrammen ausgelesen werden können, die eigentliche Arbeit des Programms aber nicht beeinflussen. Diese Metainformationen werden in Java als Annotationen bezeichnet.

An verschiedenen Stellen in diesem Buch sind wir bereits mit Annotationen in Berührung gekommen. So sind beispielsweise die zusätzlichen Tags @param oder @return für das JavaDoc-Werkzeug nichts anderes als zusätzliche Informationen, die dazu verwendet werden können, die Schnittstellen eines Programms besser zu dokumentieren (siehe Abschnitt 53.5.3).

Im Kapitel über Persistenz werden wir sehen, dass Annotationen häufig dazu verwendet werden, Konfigurationsinformationen für zusätzliche Frameworks zusammen mit dem betreffenden Code abzulegen.

Ebenso leicht ist es, eigene Annotationen zu entwickeln und diese anschließend über das Reflection API auszuwerten. Wir werden dies anhand einer eigenen Annotation demonstrieren, die es gestattet, Revisionsinformationen im Quellcode abzulegen, die Auskunft darüber geben, wann und von wem eine Klasse oder eine Methode zuletzt verändert wurde.

45.6.2 Eine einfache Annotation

Die Annotationsklasse

Bevor wir die Revisionsinformationen in unsere Klassen integrieren und über das Reflection API auswerten können, müssen wir sie zunächst definieren. Da Annotationen ähnlich wie Interfaces zusätzliche Informationen über eine Klasse zur Verfügung stellen, lag es nahe, sie auf ähnliche Art und Weise zu definieren:

001 /**
002  * Eine einfache Annotation
003  */
004 public @interface Revision
005 {
006 }
Listing 45.10: Eine einfache Annotation

Bis auf das vorangestellte @-Zeichen unterscheidet sich die Definition einer Annotation also nicht von der eines Interface. Im Gegensatz zu reinen Interfaces kann eine Annotation allerdings nicht nur für Klassen, sondern auch für andere Sprachelemente verwendet werden.

Verwendung von Annotationen

Annotationen dienen dazu, zusätzliche Informationen im Quellcode zu hinterlegen. Dabei können folgende Elemente annotiert werden:

Um beispielsweise die in Abschnitt 45.4 beschriebene Klasse PrintableObject und die von dieser definierte Methode toString mit der zusätzlichen Revisionsinformation zu versehen, schreiben wir ihren Namen einfach vor die Liste der vorhandenen Modifier:

001 import java.lang.reflect.*;
002 
003 @Revision public class PrintableObject
004 {
005   @Revision public String toString()
006   {
007   ...
008   }
009 }
Listing 45.11: Verwendung der Annotation

45.6.3 Annotationen mit einem Wert

Die Klasse besitzt nun zwar eine Annotation, wirklich brauchbare Informationen sind darin jedoch noch nicht untergebracht. Sinnvoll wäre es beispielsweise, wenn die Revisionsinformationen an PrintableObject und toString beispielsweise auch Autor und Datum der letzten Änderung enthalten würden. Wir wollen die Definition dazu zunächst wie folgt erweitern:

001 /**
002  * Eine Annotation mit einer Variablen
003  */
004 public @interface Revision
005 {
006   String value();
007 }
Listing 45.12: Annotation mit einer Variablen

Die Annotation besitzt nun den Parameter value, um die gewünschte Zusatzinformation aufzunehmen. Der Wert dieses Parameters wird bei der Verwendung der Annotation übergeben:

001 import java.lang.reflect.*;
002 
003 @Revision("Wurde zuerst geändert")
004 public class PrintableObject
005 {
006   @Revision("Wurde anschließend geändert")
007   public String toString()
008   {
009   ...
010   }
011 }
Listing 45.13: Zuweisen von annotierten Werten

Nun haben wir die beiden Annotationen mit unterschiedlichen Werten parametrisiert, die später auch ausgewertet werden können.

45.6.4 Beliebige Schlüssel-Wert-Paare in Annotationen

Der Schlüssel value stellt einen Spezialfall für ein Standardattribut dar. Eine Annotation kann nämlich nicht nur ein einziges Attribut mit dem Namen value haben, sondern beliebig viele mit beliebigen Namen. Diese müssten dann sowohl bei der Definition als auch beim Aufruf explizit angegeben werden. Nur, wenn wir den Namen des Attributs bei der Verwendung der Annotation nicht angeben, weist Java den übergebenen Wert dem Attribut value zu.

Alternativ wäre auch folgende Schreibweise möglich gewesen:

001 import java.lang.reflect.*;
002 
003 @Revision(value = "Wurde zuerst geändert")
004 public class PrintableObject
005 {
006   @Revision(value = "Wurde anschließend geändert")
007   public String toString()
008   {
009   ...
010   }
011 }
Listing 45.14: Zuweisen von annotierten Werten

Wir können unsere Revisionsannotation nun wie folgt verfeinern:

001 /**
002  * Annotation mit mehreren Variablen
003  */
004 public @interface Revision
005 {
006   int id();
007   String name();
008   String vorname();
009   String notizen();
010 }
Listing 45.15: Komplexe Annotation

Das unspezifische Attribut value ist nun einer Reihe von explizit benannten Einzelattributen gewichen, die natürlich beim Aufruf auch übergeben werden müssen:

001 import java.lang.reflect.*;
002 
003 @Revision( id = 1, name = "Krüger", vorname = "Guido", 
004            notizen = "Klasse erstellt")
005 public class PrintableObject
006 {
007   @Revision( id = 2, name = "Stark", vorname = "Thomas", 
008 	           notizen = "Methode hinzugefügt")
009   public String toString()
010   {
011   ...
012   }
013 }
Listing 45.16: Zuweisen von annotierten Werten

Die Attribute einer Annotation dürfen folgende Typen besitzen:

Die Liste ist auf diese Typen beschränkt, weil der Compiler nur mit ihnen die nötigen Typüberprüfungen vornehmen kann.

45.6.5 Standardwerte für Attribute

Die Spezifikation für Annotationen schreibt vor, dass alle definierten Attribute einer Annotation auch verwendet werden müssen. Allerdings erlaubt sie es auch, optionale Attribute zu definieren, so dass auf diese Weise Standardinhalte festgelegt werden können. Wir demonstrieren dies, indem wir die Notizen unserer Annotation mit einem Leerstring vorbelegen:

001 /**
002  * Annotation mit mehreren Variablen
003  */
004 public @interface Revision
005 {
006   int id();
007   String name();
008   String vorname();
009   String notizen() default "";
010 }
Listing 45.17: Komplexe Annotation

Nun ist die Angabe des Attributs notizen bei der Verwendung von Annotationen optional, da der Wert bereits mit einem leeren String vorbelegt ist.

45.6.6 Einschränken von Annotationen

Einschränken der Verwendbarkeit

Bisher haben wir noch keinerlei Einschränkungen für unsere Annotation definiert und können sie daher für alle oben genannten Sprachelemente verwenden. Da jedoch nicht jede Annotation für jedes Element sinnvoll ist, können wir ihre Verwendung bei Bedarf mit Hilfe einer eigenen Annotation @Target einschränken.

Um unsere Annotation beispielsweise auf Klassen und Methoden zu beschränken, ergänzen wir sie wie folgt:

001 import java.lang.annotation.Target;
002 import java.lang.annotation.ElementType;
003 
004 // Diese Annotation ist auf Klassen und Methoden beschränkt
005 @Target({ElementType.TYPE, ElementType.METHOD})
006 public @interface Revision
007 {
008   int id();
009   String name();
010   String vorname();
011   String notizen() default "";
012 }
Listing 45.18: Einschränken der Verwendbarkeit

Mögliche Werte des Aufzählungstyps ElementType sind:

Einschränken der Sichtbarkeit

Die Verwendungsmöglichkeiten von Annotationen reichen von Zusatzinformationen für Dokumentationstools, die ausschließlich den Java-Quelltext lesen, bis hin zu Konfigurationsdaten, die von der Java Virtual Machine gelesen werden und deren Verhalten beeinflussen. Um nicht allen möglichen »Nutzern« alle Annotationen zur Verfügung stellen zu müssen, lassen sich diese mit Hilfe der Annotation @Retention in drei Kategorien einteilen:

Attribut Verwendung
SOURCE Diese Informationen sind nur Bestandteil der Source-Datei und werden nicht in die Klassen einkompiliert.
CLASS Diese Informationen werden vom Compiler in die Klassendatei integriert, stehen aber nicht zur Laufzeit zur Verfügung.
RUNTIME Diese Informationen werden vom Compiler in die Klassendatei integriert und können zur Laufzeit über das Reflection API ausgelesen werden.

Tabelle 45.2: Sicherbarkeitsattribute

Wenn keine Angabe zur Retention gemacht wird, wählt der Compiler CLASS als Standardwert aus. Falls unsere Annotation auch zur Laufzeit zur Verfügung stehen soll, würden wir ihre Definition beispielsweise wie folgt erweitern:

001 import java.lang.annotation.Target;
002 import java.lang.annotation.ElementType;
003 import java.lang.annotation.Retention;
004 import java.lang.annotation.RetentionPolicy;
005 
006 // Diese Annotation ist auf Klassen und Methoden beschränkt
007 @Target({ElementType.TYPE, ElementType.METHOD})
008 
009 // Die Information soll auch zur Laufzeit zur Verfügung stehen
010 @Retention(RetentionPolicy.RUNTIME)
011 public @interface Revision
012 {
013   int id();
014   String name();
015   String vorname();
016   String notizen() default "";
017 }
Revision.java
Listing 45.19: Vollständige Annotation

Über die zusätzliche Annotation @Documented kann gesteuert werden, ob die Verwendung der Annotation auch in der JavaDoc-Dokumentation angezeigt werden soll. Und über die Annotation @Inherited gibt man an, ob eine annotierte Klasse diese Zusatzinformationen auch an davon abgeleitete Klassen vererben soll.

 Tip 

45.6.7 Auslesen von Annotationen

Da dies ein Kapitel über Reflection ist, wollen wir noch kurz zeigen, wie Annotationen mit ihrer Hilfe zur Laufzeit ausgelesen werden können. Das ist nicht schwieriger als das Ermitteln der von einer Klasse implementierten Methoden oder ihrer Modifier, denn das Reflection API stellt zu diesem Zweck das Interface AnnotatedElement zur Verfügung. Es wir von nahezu allen Reflection-Typen wie Class, Method oder Field implementiert und besitzt folgende Methoden:

boolean isAnnotationPresent(
  Class<? extends Annotation> annotationClass
);

<T extends Annotation> T getAnnotation(
  Class<T> annotationClass
);

Annotation[] getAnnotations();

Annotation[] getDeclaredAnnotations();
java.lang.reflect.AnnotatedElement

Mit Hilfe der Methode isAnnotationPresent kann festgestellt werden, ob das Element um die übergebene Annotation erweitert wurde, und über die Methode getAnnotation ist es möglich, das Annotations-Objekt auszulesen.

Die beiden letzten Methoden geben alle zu einem Element gehörenden Annotationen in Form eines Arrays zurück. Der Unterschied zwischen beiden ist, dass getDeclaredAnnotations nur die tatsächlich an das Element angehängten Metainformationen zurückgibt, während getAnnotations auch die geerbten Annotationen umfasst. Besitzt das Element keinerlei Annotationen, liefern beide Methoden ein Array der Länge null.


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de