Samstag, 4. Februar 2012


Artikel

Januar 2005 | Artikel

Gewichtige Anmerkungen

(Link zum Artikel: http://www.entwickler.de/jaxenter//000659)

Annotations - Neuerung der J2SE 5

Text: Dirk Frischalowski
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Annotations sind eine der Neuerungen der J2SE 5. Sie werden häufig auch als Metadaten bezeichnet. Sie dienen aber nicht nur der Vergrößerung Ihres Quelltextes, sondern können mittels Reflection, dem apt-Tool oder anderer Mechanismen durch Anwendungen ausgewertet werden. Anwendungsgebiete sind z.B. die automatisierte Erzeugung von Quellcode, z.B. Stubs, oder die Möglichkeit, Projektinformationen zu sammeln.

Annotations können für Variablen, Methoden, deren Parameter, Aufzählungen, Interfaces, Klassen und Packages eingesetzt werden. Der Vorteil von Annotations gegenüber anderen Möglichkeiten der Bereitstellung von Informationen liegt in der fest definierten Funktionsweise und den Zugriffsmöglichkeiten über ein API. Über Annotations erhalten Sie einen Mechanismus, um zukünftig Informationen zur automatisierten Auswertung weiterzugeben. Im Listing 1 wird die Verwendung der vordefinierten Annotation deprecated gezeigt. Diese kennzeichnet z.B. eine Klasse oder Methode als veraltet. Als deprecated markierte Methoden sollten nicht mehr in Anwendungen verwendet werden. Der Compiler (in diesem Fall die externe Anwendung) erzeugt eine Warnung beim Übersetzen. Beachten Sie, dass zum Übersetzen die Option -source 1.5 beim Kompilieren angegeben werden muss, z.B. javac -source 1.5 RufeVeraltet.java.

  1. class Veraltet
  2. {
  3. @Deprecated
  4. public void testMethod()
  5. {
  6. }
  7. }
  8. public class RufeVeraltet
  9. {
  10. public static void main(String args[])
  11. {
  12. new Veraltet().testMethod();
  13. }
  14. }
Anwendungsbeispiele
Annotations können Informationen für andere Anwendungen bereitstellen. Diese Anwendungen generieren aufgrund der Informationen in den Annotations Interfaces und/oder Klassen. Dies kann bei der Entwicklung von EJBs oder RMI-Anwendungen eingesetzt werden. Entwickler können über Annotations für eine Methode, Klasse usw. Informationen hinterlegen. Diese können automatisiert gesammelt und ausgewertet werden, z.B. um eine automatisierte Projektverwaltung zu realisieren.
Verwendung
Eine Annotation wird direkt vor das betreffende Element, z.B. eine Methode, geschrieben. Es ist aber auch möglich, eine Annotation und die Angabe der Modifizierer des Elements beliebig zu tauschen. So sind die Deklarationen @Deprecated public int und public @Deprecated int beide korrekt. Dem Namen einer Annotation wird immer das Zeichen @ vorangestellt. Obwohl zwischen dem Zeichen @ und dem Namen der Annotation Leerzeichen erlaubt sind, wird diese Schreibweise normalerweise nicht verwendet. Annotations, die keine Parameter benötigen, werden in der Regel ohne die Angabe von runden Klammern geschrieben. Die Verwendung von @AnnotationName ist damit die Kurzform von @AnnotationName(). Weiterhin darf eine Annotation nur einmal für ein bestimmtes Element angegeben werden. Annotations können auch Parameter besitzen. Diese werden in Klammern angegeben und im Falle mehrerer Parameter durch Kommata getrennt. Die Parameter entsprechen dabei immer einem so genannten Name-Wert-Paar.
  1. @Deprecated
  2. @MeineAnnotation(parameter1="wert1", parameter2=wert2)
  3. public void eineMethod()
  4. { ...
Benötigte Packages
Das Interface Annotation und die bereits vordefinierten Annotations Documented, Inherited, Retention und Target stammen aus dem Package java.lang.annotation. Zwei weitere vordefinierte Annotations, Deprecated und Overrides, befinden sich im Package java.lang. Möchten Sie über Reflection auf Annotations zugreifen, benötigen Sie zusätzlich das Package java.lang.reflect.
Vordefinierte Annotations
Es sind in der J2SE 5.0 bereits sechs Annotation-Typen vordefiniert. Diese unterteilen sich in Meta- und Standard-Annotations. Die beiden Standard-Annotations Deprecated und Overrides dienen eher allgemeinen Aufgaben. Über die Annotation Deprecated können Sie z.B. Methoden oder Klassen kennzeichnen, die nicht mehr verwendet werden sollten. Der Compiler meldet bei deren Verwendung eine Warnung der Form: Note: ... uses or overrides a deprecated API. Eine Methode, die über die Annotation Overrides gekennzeichnet wird, muss eine Methode einer Basisklasse überschreiben. Ansonsten erzeugt der Compiler einen Fehler, z.B. wenn eine solche Methode nicht in der Basisklasse existiert. Beide Annotations besitzen keine Parameter.

Meta-Annotations werden bei der Definition neuer Annotation-Typen eingesetzt. Damit ein Annotation-Typ von Javadoc bei der Erzeugung der Dokumentation automatisch berücksichtigt wird, wird die Annotation Documented verwendet. Durch die Angabe von Inherited wird der Annotation-Typ automatisch vererbt. Wird in einer Klasse keine Annotation gefunden, wird rekursiv in deren Basisklasse danach gesucht.
Die beiden folgenden Annotation-Typen verwenden Parameter. Mittels der Angabe Retention definieren Sie die Gültigkeitsdauer einer Annotation. Dazu verwenden Sie Konstanten der Aufzählung java.lang.annotation.RetentionPolicy, wobei immer nur genau ein Wert angegeben werden darf. Folgende Konstanten stehen zur Verfügung:
  • CLASS: Annotations werden in die class-Datei aufgenommen, aber nicht von der JVM geladen (Standardeinstellung). Sie können deshalb nicht über das Reflection API auf die Annotation zugreifen.
  • RUNTIME: Annotations werden in die class-Datei aufgenommen und von der JVM geladen. Sie stehen somit über Reflection zur Verfügung.
  • SOURCE: Der Compiler entfernt die Annotations, d.h., sie entsprechen in ihrer Wirkung den Kommentaren. Anwendungen, die den Java-Quelltext auswerten, können jedoch Informationen daraus entnehmen, z.B. das Tool apt. Die Annotation Deprecated ist z.B. von diesem Typ.

Über ein Target geben Sie die Elemente (Klasse, Methode, Package ...) an, denen ein Annotation-Typ zugeordnet werden kann. Wird kein Ziel angegeben, kann der Annotation-Typ allen Elementen zugeordnet werden. Über Konstanten der Aufzählung java.lang.annotation.ElementType geben Sie die betreffenden Typen an, z. B. METHOD,PACKAGE oder TYPE.
Eigene Annotation-Typen
Zusätzlich zu den vordefinierten Annotation-Typen können auch eigene erzeugt werden. Die Vorgehensweise besteht in der Definition eines Interfaces, das den Typ der Annotation festlegt. Über Meta-Annotations legen Sie die Funktionsweise fest. Nach dem Übersetzen des Interfaces können Sie eigene Annotations genauso wie die vordefinierten einsetzen.

Definieren Sie also im ersten Schritt ein Interface. Im Gegensatz zur üblichen Interface-Deklaration wird hier zusätzlich das Zeichen @ vorangestellt, z.B. @interface. Es erweitert damit automatisch das Interface java.lang.annotation.Annotation. Soll die Annotation Parameter besitzen, erzeugen Sie für jeden Parameter eine Methode mit dem entsprechenden Rückgabetyp. Der Name der Methode entspricht dabei dem Namen des Parameters. Die Methoden erwarten keine Parameter, besitzen keine throw-Klausel und dürfen als Rückgabetyp alle primitiven Datentypen, String, Class, Aufzählungen, Annotation-Typen und eindimensionale Arrays liefern. Optional können Standardwerte für eine Methode angegeben werden. Fügen Sie dazu das Schlüsselworts default, gefolgt von dem Standardwert an den Methodennamen an. Geben Sie für eine Methode keinen Standardwert an, muss zwingend für den Parameter bei der Angabe der Annotation ein Wert übergeben werden.
Beispielanwendung
Die Annotation im folgenden Listing dient der Vergabe einer Bemerkung für Methoden, Klassen und Interfaces sowie Parameter von Methoden. Dazu wurden die entsprechenden Konstanten über die Meta-Annotation Target übergeben. Über die Angabe @Retention mit dem Wert RetentionPolicy.RUNTIME wird sichergestellt, dass zur Laufzeit über Reflection die Informationen der Annotation ausgelesen werden können. Der Name der Annotation ist Bemerkung und kann nach dem Übersetzen des Interfaces in anderen Klassen genutzt werden. Da als Methodenname value() verwendet wurde, kann eine verkürzte Schreibweise bei der Verwendung der Annotation angewendet werden, z.B. @Bemerkung("..."). Der Name des Parameters muss dazu nicht mehr angegeben werden.
  1. import java.lang.annotation.*;
  2. @Documented
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Target({ElementType.METHOD, ElementType.TYPE,
  5. ElementType.PARAMETER})
  6. public @interface Bemerkung
  7. {
  8. String value();
  9. }
Annotation verwenden
Befindet sich die Annotation in einem Package welches nicht standardmäßig geladen wird, muss dieses importiert werden. Die Angabe einer Annotation beginnt immer mit dem Zeichen @, gefolgt vom Namen der Annotation. Optional können Sie zwischen @ und dem Namen Leerzeichen angeben. Für jede Methode des Interfaces der Annotation, die keinen Standardwert besitzt, muss ein Parameter der Form Methodenname=Wert in Klammern angegeben werden. Der Wert hat dabei denselben Typ wie der Rückgabetyps der korrespondierenden Methode im Interface. Da die Parameter über den Methodennamen eindeutig benannt sind, spielt die Reihenfolge keine Rolle.
Das Listing 2 verwendet die komplexere Annotation Todo aus Listing 1. Der Parameter beschreibung wird nur mit einem Eintrag in das String Array eingefügt. In diesem Fall können Sie die Klammern {} auch weglassen. Dem Parameter klasse wird ein Klassenobjekt zugewiesen und der Wertigkeit ein Zahlenwert. Um einem Annotation-Typ zu belegen, wird der Name der Annotation mit dem vorangestellten Zeichen @ verwendet. In Klammern werden dann die Parameter wiederum in der Form Name=Wert angegeben. Da die Annotation Bemerkung eine einfache Annotation ist, können der Name des Parameters weggelassen und damit die verkürzte Schreibweise verwendet werden.

Listing 1
  1. import java.lang.annotation.*;
  2. @Documented
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE})
  5. public @ interface Todo
  6. {
  7. String[] beschreibung() default {""};
  8. Class klasse();
  9. int wertigkeit() default 1;
  10. Bemerkung bem();
  11. }
Listing 2
  1. @Todo(beschreibung={"Erste Versuche mit Annotations"},
  2. klasse=TodoAnnotationTest.class,
  3. wertigkeit=3,
  4. bem=@Bemerkung("nichts"))
  5. public <Type> Methodenname()
  6. ...
Annotations für Packages
Packages werden über eine spezielle Vorgehensweise mit Annotations versehen. Dazu wird im Verzeichnis des Packages eine Datei mit dem Namen package-info.java bereitgestellt. Sie enthält lediglich Annotations, Dokumentationskommentare sowie die package-Anweisung. Da der Dateiname package-info.java kein gültiger Klassenname ist, wird diese Datei nur zu Informationszwecken für ein Package genutzt. Vorzugsweise wird jetzt diese Datei von Javadoc statt der Datei package.html für Informationen zum Package genutzt. Die Datei package-info.java hat beispielsweise folgenden Aufbau:
  1. @Todo(beschreibung={"Erste Versuche mit Annotations"},
  2. klasse=TodoAnnotationTest.class,
  3. wertigkeit=3,
  4. bem=@Bemerkung("nichts"))
  5. /**
  6. * Das Package der Annotation-Bespiele
  7. * @author Dirk F.
  8. */
  9. package com.javamagazin.annos;

Um eine Dokumentation mit Javadoc und diesen Informationen in einem Unterverzeichnis ..\HTML zu erstellen, rufen Sie das Tool folgendermaßen auf (Sie befinden sich dazu im ..\com übergeordneten Verzeichnis): javadoc -source 1.5 -d HTML -subpackages com.javamagazin.annos.
Zugriff zur Laufzeit
Für den Zugriff auf Annotations zur Laufzeit wird das Reflection API benötigt. Die Klassen Class, Constructor, Field, Method und Package implementieren dazu das Interface AnnotatedElement und stellen damit die vier Methoden des folgenden Listings zur Verfügung (die Syntax ist vereinfacht dargestellt, da sie eigentlich auf Generics beruht).
  1. Annotation getAnnotation (Class annotationTyp)
  2. Annotation[] getAnnotations ()
  3. Annotation[] getDeclaredAnnotations ()
  4. boolean isAnnotationPresent (Class annotationTyp)

Der Reihenfolge nach liefern die Methoden
  • alle Annotations eines Elements eines Annotation-Typs,
  • alle Annotations inklusive der vererbten,
  • alle Annotations ohne vererbte und
  • einen Rückgabewert true oder false, falls der übergebene Annotation-Typ für das Element verwendet wurde.

Um mithilfe des Reflection API auf Annotations zuzugreifen, holen Sie sich zuerst eine Referenz auf das betreffende Element. Danach können Sie optional über die Methode isAnnotationPresent() prüfen, ob das Element eine bestimmte Annotation besitzt. Durch die Angabe eines Annotation-Typs können Sie jetzt eine bestimmte Annotation auslesen. Der Zugriff auf die Parameterwerte erfolgt über die gleichnamigen Methoden des Interfaces der Annotation. Das vollständige Beispiel zeigt Listing 3.

Listing 3
  1. import java.lang.reflect.*;
  2. import java.lang.annotation.*;
  3. public class TodoAnnotationTest
  4. {
  5. @Todo(beschreibung={"Erste Versuche mit Annotations"},
  6. klasse=TodoAnnotationTest.class,
  7. wertigkeit=3
  8. bem=@Bemerkung("keine"))
  9. public boolean berechne()
  10. {
  11. return false;
  12. }
  13. public static void main(String args[])
  14. {
  15. Method[] methoden = TodoAnnotationTest.class.getMethods();
  16. for(Method m: methoden)
  17. {
  18. if(m.isAnnotationPresent(Todo.class))
  19. {
  20. System.out.println("Methode " + m.getName());
  21. System.out.println("-----------------------");
  22. Todo td = m.getAnnotation(Todo.class);
  23. System.out.println(td.beschreibung()[0]);
  24. System.out.println(td.klasse().getName());
  25. System.out.println(td.wertigkeit());
  26. System.out.println(td.bem().value());
  27. }
  28. }
  29. }
  30. }
Annotation Processing Tool - apt
Für die Verarbeitung von Annotations wird ein neues Tool mit Namen apt unter [InstallDir]\bin mitgeliefert. Darüber können Sie über die Kommandozeile Annotations auswerten und z.B. eigene Implementierungen zur Generierung von Java-Sourcecode einsetzen. Bei seiner Ausführung analysiert apt den betreffenden Java-Sourcecode und ermittelt die enthaltenen Annotations. Danach sucht apt nach Annotation Processor Factories. Diese werden in einer über die Option -factory angegebenen Klasse oder dem Klassenpfad gesucht. Den Factories übergibt apt die gefundenen Annotations. Eine solche Factory kann dann einen Annotation Processor zurückgeben, über den die Annotation verarbeitet wird. Dabei ist es jetzt möglich, basierend auf den Informationen der Annotation Sourcecode zu generieren. Der Ausgangscode und der generierte Code können im Anschluss übersetzt werden. Damit erhalten Sie einen Mechanismus, der nur auf dem Ausgangscode basiert und alle anderen Dateien daraus automatisch erzeugt. Die Verwendung von apt ist mit den folgenden Schritten verbunden:
  • Erzeugen Sie eine Annotation Processor Factory.
  • Erzeugen Sie einen Annotation Processor, der über die Factory zurückgegeben wird. Übergeben Sie die Factory über den Parameter -factory an das apt-Tool. Eine weitere Möglichkeit besteht in der Erzeugung eines JAR-Archivs und dessen Integration in den Klassenpfad.

Die benötigten Klassen und Interfaces zur Erstellung der Factory und des Prozessors finden Sie in den Packages com.sun.mirror.apt, com.sun.mirror.declaration, com.sun.mirror.type und com.sun.mirror.util. Dazu müssen Sie beim Übersetzen das Archiv [InstallDir]\lib\tools.jar in den Klassenpfad aufnehmen, z.B. javac -source 1.5 -classpath D:\j2sdk1.5.0\lib\tools.jar AptF.java. Die Hilfe zu apt, dem Mirror API und ein Anwendungsbeispiel finden Sie unter \j2sdk1.5.0\docs\guide\apt\index.html.
Zusammenfassung
Annotations werden in Zukunft eine große Rolle bei der dynamischen Generierung von Java-Quellcode spielen. Durch die einfache Beschreibung der benötigten Informationen über Annotations wird sich der Aufwand auf der Entwicklerseite verringern, weniger fehleranfällig und besser lesbar werden. Beachten Sie aber, dass weder die Java-Quelltexte noch die class-Dateien kompatibel mit älteren Versionen sind.
Dirk Frischalowski arbeitet als Selbstständiger im Bereich der Anwendungsentwicklung mit Delphi, Java und .NET. Weitere Schwerpunkte sind das Schreiben von Artikeln und Softwaredokumentationen, Vorträge auf verschiedenen Veranstaltungen sowie die Durchführung von Schulungen und Consulting.

Links und Literatur
[1] jcp.org/aboutJava/communityprocess/review/jsr175/
[2] java.sun.com/developer/technicalArticles/releases/j2se15/

Kommentare