From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+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.
+
+
+
+
+
+Bevor wir die Revisionsinformationen in unsere Klassen integrieren
+und über die Reflection API auswerten können, müssen
+wir sie zunächst definieren.
+
+
+
+
+
+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:
+
+
+
+
+
+
+ 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
+
+43.6.2 Eine einfache Annotation
+
+Die Annotationsklasse
+
+
+
+
+Listing 43.10: Eine einfache Annotation
+
+
+
+
+
+001 /**
+002 * Eine einfache Annotation
+003 */
+004 public @interface Revision
+005 {
+006 }
+
+
+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. + + + + +
+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 }
+
+ |
+
+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. + + + + +
+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 }
+
+ |
+
+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 }
+
+ |
+
+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. + + + + +
+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 }
+
+ |
+
+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 }
+
+ |
+
+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 }
+
+ |
+
+Dies sieht doch schon nach brauchbaren Informationen aus und bringt +uns zu der Frage, welche Datentypen Annotations-Attribute besitzen +dürfen. + + + + +
+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. + + + + +
+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 }
+
+ |
+
+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. + + + + +
+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. + + + + +
+Ü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 }
+
+
+ |
+
+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. + + + + +
+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 | +
+Ü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. + + + + +
+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.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |