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

43.6 Annotationen oder Metainformationen im Javacode



43.6.1 Metainformationen

Was sind eigentlich Metainformationen und wofür sind sie gut? Nun, Metainformationen sind einfach Informationen über Informationen und können dafür verwendet werden, zusätzliches Wissen abzulegen. In unserem Fall handelt es sich dabei um zusätzliches Wissen über Javaprogramme, das zusammen mit dem Quellcode abgelegt werden kann, die Arbeitsweise eines Programms jedoch nicht beeinflusst.

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

Im Kapitel über Persistenz werden wir außerdem sehen, dass Annotationen häufig dazu verwendet werden, Konfigurationsinformationen für zusätzlichen Frameworks zusammen mit dem betreffenden Code abzulegen und uns so weitestgehend von der Last zusätzlicher Konfigurationsdateien zu befreien. Es ist jedoch ebenso leicht eigene Annotationen zu entwickeln und ihre Informationen anschließend über das Reflection API auswerten. Wir werden dies anhand einer eigenen Annotation demonstrieren, die es gestattet, Revisionsinformationen im Quellcode zu hinterlegen. Diese geben Auskunft darüber , wann und von wem eine Klasse oder eine Methode zuletzt verändert worden sind.

43.6.2 Eine einfache Annotation

Bevor wir die Revisionsinformationen in unsere Klassen integrieren und über die Reflection API auswerten können, müssen wir sie zunächst definieren.

Die Annotationsklasse

Mit Annotationen verhält es sich ähnlich wie mit einem Interface, das in gewisser Weise auch zusätzliche Informationen über eine Klasse zur Verfügung stellt. So ist es kaum verwunderlich, dass eine Annotation ganz ähnlich definiert wird:

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

Bis auf das vorangestellte @-Zeichen unterscheidet sich die Definition einer Annotation also nicht von der eines normalen Interfaces, wie es in Kapitel 9 beschrieben wird. Im Gegensatz zu reinen Interfaces kann eine Annotation allerdings nicht nur für Klassen verwendet werden.

Verwendung von Annotationen

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

Da nicht alle Annotationen bei allen Elementen Sinn machen, werden wir später sehen, wie wir diese Liste einschränken können. Um nun beispielsweise die in Abschnitt 43.4 beschriebene Klasse PrintableObject und die von dieser definierte Methode toString mit Zusatzinformationen zu versehen schreiben wir ihren Namen dieser 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 43.11: Verwendung der Annotation

Geschafft! Sie können Ihre Annotationen nun genau wie die anderen Modifier verwenden und einfach vor das zu annotierende Element schreiben. Schon ist der Code mit den gewünschten Zusatzinformationen angereichert.

Der eine oder andere Leser könnte jetzt anmerken, dass die Klasse zwar nun annotiert ist, jedoch noch keine wirklichen Zusatzinformationen enthält. Zwar sind ist die Klasse PrintableObject und die Methode toString annotiert, allerdings wissen wir nicht vom wem und wann. Diese Informationen werden wir nun ergänzen.

43.6.3 Annotationen mit einem Wert

Um tatsächlich zusätzliche Informationen ablegen zu können, müssen wir die Annotation nun gewissermaßen mit einer Variablen ausstatten. Dazu erweitern wir die Definition wie folgt:

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

Die Annotation besitzt nun die Variable value, die die eigentliche Information aufnehmen kann und im Gegenzug auch belegt werden muss. Der Wert der Variablen value wird bei der Verwendung der Annotation in der Klasse PrintableObject gesetzt.

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 43.13: Zuweisen von annotierten Werten

Die Annotationen sind zwar nun jeweils eine Zeile nach oben gerückt, aber für den Compiler steht sie immer noch in der Liest der Modifier. Wir können Javaklassen prinzipiell auch als Einzeiler formulieren. Um die Übersicht zu wahren, ist es aber von Vorteil, lange Annotationen in eine separate Zeile zu stellen.

Wie Sie sehen, sind nun beide Annotationen mit unterschiedlichen Werten ausgestattet, die wir später auch auswerten können.

43.6.4 Beliebige Schlüssel-Wert-Paare in Annotationen

Das Element value, welches in den vorangegangenen Listings den Wert aufnimmt ist zwar praktisch, aber um detailliertere Informationen ablegen zu können, müssten wir nun weitere Annotationen definieren, was in einem Wust von zusätzlichen »Modifizierern« enden würde. Um dies zu vermeiden und zusammengehörende Informationen in einer Annotation zusammenzufassen, können diese beliebig viele Informationen aufnehmen.

Der Schlüssel value stellt dabei nur einen Spezialfall für ein Standard-Attribut dar. Wenn wir den Namen des Attributs nicht angeben weißt Java den Wert der Annotation 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 43.14: Zuweisen von annotierten Werten

Nachdem wir die einzelnen Attribute einer Annotation nun genau adressieren können, verfeinern wir diese folgendermaßen:

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 43.15: Komplexe Annotation

Das unspezifische Attribut value ist nun ein Reihe von detaillierten Einzelattributen gewichen, die natürlich auch gefüllt werden möchten:

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 43.16: Zuweisen von annotierten Werten

Dies sieht doch schon nach brauchbaren Informationen aus und bringt uns zu der Frage, welche Datentypen Annotations-Attribute besitzen dürfen.

Unterstützte Datentypen

Die Attribute einer Annotation dürfen folgende Typen besitzen:

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

43.6.5 Standardwerte für Attribute

Die Spezifikation für Annotation schreibt vor, dass alle definierten Attribute einer Annotation auch verwendet werden müssen. Um dennoch optionale Attribute definieren zu können, gestattet es die Spezifikation, Standardwerte festzulegen. Wir demonstrieren dies, indem wir die Notizen der Annotationen nun mit einem leeren String 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 43.17: Komplexe Annotation

Nun ist die Angabe des Attributes notizen bei der Verwendung von Annotationen optional, da der Wert bereits vorinitialisiert ist. Gegebenenfalls wird der Default-Wert natürlich mit dem jeweils Angegebenen überschrieben.

43.6.6 Einschränken von Annotationen

Bisher haben wir noch keinerlei Einschränkungen für unsere Annotation definiert und können diese deshalb sowohl für Pakete wie auch für lokale Variablen verwenden. Da jedoch nicht jede Annotation für jedes Element sinnvoll ist, können wir ihre Verwendung bei Bedarf einschränken. Und warum sollten wir hierfür etwas anderes verwenden als wiederum eine Annotation.

Einschränken der Verwendbarkeit

Über die Annotation @Target können wir die Verwendbarkeit einer Annotationen auf bestimmte Elemente einschränken. Mögliche Werte sind:

Da wir uns am Anfang des Kapitels entschlossen haben die Annotation auf Klassen und Methoden zu beschränken, ergänzen wir das Listing der Revision ein weiteres Mal:

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 43.18: Einschränken der Verwendbarkeit

An diesem Beispiel können Sie außerdem die Verwendung von Arrays und Aufzählungen (Enum) als Attribut der Annotation Target nachvollziehen. Die Variable value dieser Annotation enthält ein Array aus Aufzählungsobjekten, deren Werte in einer geschweifen Klammer und durch Kommata getrennt definiert werden. Wenn Sie das Target nicht spezifizieren, kann die Annotation universell verwendet werden.

Einschränken der Sichtbarkeit

Die Verwendungsmöglichkeiten von Annotationen reichen von Zusatzinformationen für Dokumentationstools bis hin zu Konfigurationsdaten, die das Laufzeitverhalten der Java Virtual Machine beeinflussen können. Um nicht alle - möglicherweise - unnötigen Metainformationen bereitstellen zu müssen, lassen sich diese über die Annotation @Retention in 3 Kategorien einteilen:

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

Tabelle 43.2: Sicherbarkeitsattribute

Wenn Sie keine Angabe zur Retention machen, wird die RetentionPolicy CLASS als Standardwert ausgewählt. Da unsere Annotation jedoch auch zur Laufzeit zur Verfügung stehen soll, erweitern wir das Listing ein letztes Mal:

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 43.19: Vollständige Annotation

Dokumentieren der Annotation und Vererbung

Über die zusätzliche Annotation @Documented kann schließlich 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 vererbt.

43.6.7 Auslesen von Annotationen

Da dies primär ein Kapitel über Reflection, als über Annotationen ist, beenden wir diesen kleinen Ausflug mit dem Wissen, wie die zuvor hinterlegten Meta-Informationen auch zur Laufzeit ausgelesen werden können. Das ist nicht schwieriger als das Ermitteln der von einer Klasse implementierten Methoden oder ihrer Modifier, wie wir es in Abschnitt 43.3 kennengelernt haben.

Seit der Version 5 des JDK stellt das Reflection API das Interface AnnotatedElement zur Verfügung. Es wir von nahezu allen Reflection-Typen wie Class, Method oder Field implementiert und besitzt folgende Signatur:

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

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

Annotation[] getAnnotations();

Annotation[] getDeclaredAnnotations();

java.lang.reflect.AnnotatedElement

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

Die beiden letzten Methoden geben schließlich alle zu einer Element gehörenden Annotationen in Form eines Arrays zurück. Der Unterschied zwischen beiden ist, dass getDeclaredAnnotations nur die tatsächlich an das Element angehangenen Metainformationen zurückgibt, während getAnnotations auch die geerbten Annotationen einschließt. Besitzt das Element keinerlei Annotationen, ist das Resultat der Methoden ein Array der Länge 0.


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 5. Auflage, Addison Wesley, Version 5.0.2
 <<    <     >    >>   API  © 1998, 2007 Guido Krüger & Thomas Stark, http://www.javabuch.de