Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 45 - Reflection |
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.
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 } |
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.
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 } |
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 } |
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 } |
Nun haben wir die beiden Annotationen mit unterschiedlichen Werten parametrisiert, die später auch ausgewertet werden können.
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 } |
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 } |
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 } |
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.
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 } |
Nun ist die Angabe des Attributs notizen bei der Verwendung von Annotationen optional, da der Wert bereits mit einem leeren String vorbelegt ist.
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 } |
Mögliche Werte des Aufzählungstyps ElementType sind:
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 |
Ü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. |
|
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 |