Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 5. Auflage |
<< | < | > | >> | API | Kapitel 43 - Reflection |
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:
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.2 |
<< | < | > | >> | API | © 1998, 2007 Guido Krüger & Thomas Stark, http://www.javabuch.de |