Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Neues in Java 7
2 Threads und nebenläufige Programmierung
3 Datenstrukturen und Algorithmen
4 Raum und Zeit
5 Dateien, Verzeichnisse und Dateizugriffe
6 Datenströme
7 Die eXtensible Markup Language (XML)
8 Dateiformate
9 Grafische Oberflächen mit Swing
10 Grafikprogrammierung
11 Netzwerkprogrammierung
12 Verteilte Programmierung mit RMI
13 RESTful und SOAP Web-Services
14 JavaServer Pages und Servlets
15 Applets
16 Datenbankmanagement mit JDBC
17 Technologien für die Infrastruktur
18 Reflection und Annotationen
19 Dynamische Übersetzung und Skriptsprachen
20 Logging und Monitoring
21 Java Native Interface (JNI)
22 Sicherheitskonzepte
23 Dienstprogramme für die Java-Umgebung
Stichwort
Download: - openbook, ca. 21,3 MB
Buch bestellen
Ihre Meinung?
<< zurück | Galileo Computing / <openbook> / Java 7 - Mehr als eine Insel | vor >>
Java 7 - Mehr als eine Insel von Christian Ullenboom Das Handbuch zu den Java SE-Bibliotheken
Java 7 - Mehr als eine Insel Galileo Computing 1433 S., 2012, geb. 49,90 Euro, ISBN 978-3-8362-1507-7 |
| 18 Reflection und Annotationen
|  | 18.1 Metadaten
|  |  | 18.1.1 Metadaten durch JavaDoc-Tags
|  | 18.2 Metadaten der Klassen mit dem Class-Objekt
|  |  | 18.2.1 An ein Class-Objekt kommen
|  |  | 18.2.2 Was das Class-Objekt beschreibt *
|  |  | 18.2.3 Der Name der Klasse
|  |  | 18.2.4 instanceof mit Class-Objekten *
|  |  | 18.2.5 Oberklassen finden *
|  |  | 18.2.6 Implementierte Interfaces einer Klasse oder eines Interfaces *
|  |  | 18.2.7 Modifizierer und die Klasse Modifier *
|  |  | 18.2.8 Die Arbeit auf dem Feld *
|  | 18.3 Attribute, Methoden und Konstruktoren
|  |  | 18.3.1 Reflections – Gespür für die Attribute einer Klasse
|  |  | 18.3.2 Methoden einer Klasse erfragen
|  |  | 18.3.3 Properties einer Bean erfragen
|  |  | 18.3.4 Konstruktoren einer Klasse
|  |  | 18.3.5 Annotationen
|  | 18.4 Objekte erzeugen und manipulieren
|  |  | 18.4.1 Objekte erzeugen
|  |  | 18.4.2 Die Belegung der Variablen erfragen
|  |  | 18.4.3 Eine generische eigene toString()-Methode *
|  |  | 18.4.4 Variablen setzen
|  |  | 18.4.5 Bean-Zustände kopieren *
|  |  | 18.4.6 Private Attribute ändern
|  |  | 18.4.7 Methoden aufrufen
|  |  | 18.4.8 Statische Methoden aufrufen
|  |  | 18.4.9 Dynamische Methodenaufrufe bei festen Methoden beschleunigen *
|  | 18.5 Eigene Annotationstypen *
|  |  | 18.5.1 Annotationen zum Laden von Ressourcen
|  |  | 18.5.2 Neue Annotationen deklarieren
|  |  | 18.5.3 Annotationen mit genau einem Attribut
|  |  | 18.5.4 Element-Werte-Paare (Attribute) hinzufügen
|  |  | 18.5.5 Annotationsattribute vom Typ einer Aufzählung
|  |  | 18.5.6 Felder von Annotationsattributen
|  |  | 18.5.7 Vorbelegte Attribute
|  |  | 18.5.8 Annotieren von Annotationstypen
|  |  | 18.5.9 Deklarationen für unsere Ressourcen-Annotationen
|  |  | 18.5.10 Annotierte Elemente auslesen
|  |  | 18.5.11 Auf die Annotationsattribute zugreifen
|  |  | 18.5.12 Komplettbeispiel zum Initialisieren von Ressourcen
|  |  | 18.5.13 Mögliche Nachteile von Annotationen
|  | 18.6 Zum Weiterlesen
## 18.5 Eigene Annotationstypen * Die in der Java-Standardbibliothek vorgegebenen Annotationen haben entweder eine besondere Semantik für den Compiler, wie @Override oder @SuppressWarnings, oder dienen zum Beispiel zur Definition von Web-Services (@WebService, @WebMethod, ...) oder von Komponenten mit XML-Abbildung (@XmlRootElement, @XmlElement, ...). Insbesondere die Java Enterprise Edition (Java EE) macht von Annotationen fleißig Gebrauch, und es lassen sich auch neue Annotationstypen deklarieren. ### 18.5.1 Annotationen zum Laden von Ressourcen Im Folgenden wollen wir drei Annotationstypen deklarieren, die den Inhalt von Objektvariablen beschreiben. Zunächst werden die Annotationstypen selbst beschrieben, und abschließend folgt eine Klasse, die die Annotationen ausliest und die Ressourcen initialisiert. Es soll möglich sein, mit @CurrentDateResource eine Objektvariable mit dem aktuellen Datum zu belegen:
    @CurrentDateResource
    public Date now;
Ist eine Variable mit @ListOfFilesResource annotiert, so sollen alle Dateien und Unterverzeichnisse aus einem gegebenen Verzeichnis aufgelistet und damit ein Feld initialisiert werden:
    @ListOfFilesResource( "c:/" )
    String[] files;
Die Annotation @UrlResource ist die komplexeste Annotation. Sie beschreibt im einfachsten Fall eine URL mit Daten von einem HTTP-Server (mit dem URL-Protokoll file:// auch vom lokalen Dateisystem), sodass eine Variable mit dem Inhalt initialisiert werden kann:
    @UrlResource( "http://tutego.de/aufgaben/bond.txt" )
    String testFile;
Der Annotation lassen sich noch einige Attribute (Element-Wert-Paare) übergeben, sodass etwa Leerraum entfernt wird oder der String in Groß-/Kleinbuchstaben konvertiert wird:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
                  trim = true,
                  upperLowerCase = UpperLowerCase.UPPERCASE )
    public String testFile;
Zu guter Letzt lassen sich bei @UrlResource auch beliebige Konvertierer-Klassen angeben, die den Text der Ressource transformieren:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
                  converter = { RemoveNoWordCharactersConverter.class,
                                SortConverter.class } )
    public String testFile;
### 18.5.2 Neue Annotationen deklarieren Ein Annotationstyp (engl. annotation type) wird so deklariert wie eine Schnittstelle, nur steht vor dem Schlüsselwort interface ein @-Zeichen. Beginnen wir mit dem einfachsten Annotationstyp, CurrentDateResource:
    public @interface CurrentDateResource { }
Die Ähnlichkeit von neuen Annotationstypen und Schnittstellen ist so groß, dass in der Java Language Specification die Annotationen auch im Kapitel über Schnittstellen behandelt werden. (Später erfahren wir den Grund dafür: Der Compiler übersetzt die Annotationstypen in Schnittstellen.) Wo sich der Annotationstyp festmachen lässt, kann eingeschränkt werden. Im Standardfall kann er überall angeheftet werden, das heißt an beliebigen Typdeklarationen, Annotationen, Aufzählungen, Objekt-/Klassenvariablen, lokalen Variablen, Parametern, Methoden, Konstruktoren oder auch an Paketen (wobei die Syntax da etwas anders ist). Damit ist Folgendes erlaubt:
    @CurrentDateResource public Date now;
### 18.5.3 Annotationen mit genau einem Attribut Der Annotationstyp @CurrentDateResource kann mit keinem zusätzlichen Attribut versehen werden, da er in der bisherigen Schreibweise eine Markierungsannotation ist. Erlaubt sind zwar ein Paar runde Klammern hinter dem Namen und auch Kommentare, aber eben kein zusätzliches Attribut, wie es @ListOfFilesResource etwa wünscht:
    @CurrentDateResource public Date now;
    @CurrentDateResource() public Date now;
    @CurrentDateRessource( "gestern" ) public Date now;            //  Compilerfehler
Damit zusätzliche Informationen für den Pfadnamen bei @ListOfFilesResource("c:/") möglich sind, werden im Annotationstyp Deklarationen für Attribute eingesetzt, deren Schreibweise an Operationen einer Java-Schnittstelle erinnert. (Aber die Operationen dürfen keinen Parameter besitzen, die Rückgabe darf nicht void sein und kein throws besitzen. Und Operationen, die so heißen wie die Methoden aus Object, sind nicht zugelassen.) Damit ein zusätzliches Attribut den Pfadnamen annehmen kann, sieht die Deklaration des Annotationstyps ListOfFilesResource so aus:
    public @interface ListOfFilesResource
    {
      String value();
    }
Damit haben wir den zweiten Annotationstyp aus unserem Beispiel vorbereitet, und gültig wäre:
    @ListOfFilesResource( "c:/" )
    String[] files;
Fehlt das erwartete Element, also der Pfad-String, gibt es einen Compilerfehler. #### Attributtypen Das, was so wie ein Rückgabetyp einer Methode aussieht, bestimmt den Typ des Attributs und ist im begrenzten Rahmen wählbar. Der Typ muss nicht immer nur String sein. Insgesamt erlaubt Java: alle primitiven Datentypen (byte, short, int, long, float, double, boolean), aber keine Wrapper String Class. Insbesondere mit der generischen Angabe ermöglicht er eine präzise Klassenangabe. Enum-Typen andere Annotationen (was zu geschachtelten Annotationen führt) Felder von oben genannten Typen. Felder von Feldern (mehrdimensionale Felder) sind aber nicht gestattet.
Hinweis
Die Attribute sind typisiert, und fehlerhafte Typen lehnt der Compiler ab. null ist als Argument nie erlaubt. Mögliche Typkonvertierungen führt der Compiler automatisch durch:
    @ListOfFilesResource( "" ) String[] files;                // OK
    @ListOfFilesResource() String[] files;                    //  Compilerfehler
    @ListOfFilesResource( null ) String[] files;              //  Compilerfehler
    @ListOfFilesResource( 1 ) String[] files;                 //  Compilerfehler
    @ListOfFilesResource( 'C' ) String[] files;               //  Compilerfehler
    @ListOfFilesResource( "C:" + '/' ) String[] files;        // OK
### 18.5.4 Element-Werte-Paare (Attribute) hinzufügen Wenn der Annotationstyp ein Attribut mit dem Namen value deklariert, so muss keine Angabe über einen Schlüsselnamen gemacht werden. Möglich wäre das aber schon, und geschrieben würde das so:
    @ListOfFilesResource( value = "c:/" )
    String[] files;
Eine Annotation lässt sich mit einer beliebigen Anzahl von Attributen deklarieren, und das Attribut muss auch nur dann value heißen, wenn der Schlüssel nicht ausdrücklich genannt werden soll – also @ListOfFilesResource("c:/") statt @ListOfFilesResource(value = "c:/"). Ist mehr als ein Attribut nötig, muss ohnehin immer der Attributname zusammen mit der Belegung genannt werden. Wenn @ListOfFilesResource mit einem Attribut trim ausgestattet wird, sodass die gelesenen Texte automatisch vorne und hinten den Weißraum abgeschnitten bekommen, so könnte die Deklaration des Annotationstyps so aussehen:
    public @interface UrlResource
    {
      String   value();
      boolean  trim();
    }
Und in der Anwendung:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt", trim = true )
    String testFile;
### 18.5.5 Annotationsattribute vom Typ einer Aufzählung Bisher haben wir als Attributtyp String und boolean eingesetzt. Attribute dürfen auch Aufzählungen sein. Wir wollen das für @UrlResource nutzen, damit wir beim Einlesen wählen können, ob der Text in Groß- oder Kleinbuchstaben konvertiert wird:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
                  upperLowerCase = UpperLowerCase.UPPERCASE )
    String testFile;
Für die Konvertierungsart deklarieren wir zunächst eine Aufzählung und deklarieren das Attribut upperLowerCase dann genau mit dem Aufzählungstyp:
    public @interface UrlResource
    {
      public enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }

      String          value();
      UpperLowerCase  upperLowerCase();
    }
Die Aufzählung UpperLowerCase als inneren Typ zu deklarieren, ist interessant, da sie ja nicht allgemein ist, sondern ausschließlich mit der Annotation @UrlResource Sinn ergibt. ### 18.5.6 Felder von Annotationsattributen Von den unterschiedlichen Elementtypen dürfen eindimensionale Felder gebildet werden. Da es keine anderen Sammlungen gibt, stellt das Feld die einzige Möglichkeit dar, beliebig viele Elemente anzugeben. Der @UrlResource sollen beliebig viele Konvertierungsfilter zugewiesen werden. Konvertierungsfilter sind Klassen, die die Schnittstelle ResourceConverter implementieren und den eingelesenen String transformieren. Dann heißt es in der Deklaration des Annotationstyps:
    public @interface UrlResource
    {
      String value();
      Class<? extends ResourceConverter>[] converter();
    }
Der interessante Teil ist natürlich Class<? extends ResourceConverter>[]. Der setzt sich wie folgt zusammen: Da Java es nicht erlaubt, dass beliebige Attributtypen verwendet werden, bleiben bei der Angabe der Konverter nur Class-Objekte und nicht etwa ResourceConverter[]. Die Typangabe Class[] wäre nicht ausreichend, da Class mit einem generischen Typ präzisiert werden muss. Jetzt ist aber Class<ResourceConverter> auch noch nicht präzise, denn wir wollen ja nicht nur exakt den Typ RessourceConverter treffen, sondern Untertypen, also Klassen, die RessourceConverter erweitern. Damit sind wir bei Class<? extends ResourceConverter>. Da es eine Liste von Class-Angaben werden kann, muss das Paar eckiger Klammen an die Deklaration. Weisen wir zum Beispiel zwei Konverter – die Klassen wurden noch nicht vorgestellt, aber das folgt – der @UrlResource zu:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
                  converter = { RemoveNoWordCharactersConverter.class,
                                SortConverter.class } )
    public String testFile;
Bei nur einem angegebenen Konverter können die geschweiften Klammern sogar entfallen:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
                  converter = RemoveNoWordCharactersConverter.class )
Hinweis
Da ein Attribut wieder eine Annotation sein kann, ergeben sich interessante Möglichkeiten. Neben wir an, der Annotationstyp Name speichert Vor- und Nachnamen:
    @interface Name
    {
      String firstname();
      String lastname();
    }
Ein Annotationstyp Author soll Name als Elementtyp für value nutzen:
    @interface Author
    {
      Name[] value();
    }
Vor Name steht nicht das @-Zeichen. Nur in der Anwendung:
    @Author( @Name( firstname = "Christian", lastname = "Ullenboom" ) )
Hätten wir das Element nicht value, sondern etwa name genannt, müsste die Angabe so heißen:
    name = @Name( firstname = "Christian", lastname = "Ullenboom" )
Und hätten wir mehrere Autoren angegeben, würden wir Folgendes schreiben:
    @Author(
      {
        @Name( firstname = "Christian", lastname = "Ullenboom" ),
        @Name( firstname = "Hansi", lastname = "Hinterweltler" )
      } )
### 18.5.7 Vorbelegte Attribute Im bisherigen Fall mussten alle Attributbelegungen angegeben werden, und wir konnten kein Element-Werte-Paar auslassen. Die Annotationstypen ermöglichen allerdings für Attribute Standardwerte, sodass ein Wert angeben werden kann, aber nicht muss. Statt
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt", trim = false )
soll es möglich sein, trim = false wegzulasssen, weil es Standard sein soll:
    @UrlResource( value = "http://tutego.de/aufgaben/bond.txt" )
Beziehungsweise dann wieder kürzer:
    @UrlResource( "http://tutego.de/aufgaben/bond.txt" )
In der Syntax für Vorbelegungen hält dafür das Schlüsselwort default her, was auch zu einer neuen Schreibweise führt, die von den Schnittstellen abweicht. Bei unserem @UrlResource ist nur die Angabe der Textquelle vonnöten; alles andere soll mit Default-Werten belegt sein:
    public @interface UrlResource
    {
      enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }

      String value();
      boolean trim() default false;
      UpperLowerCase upperLowerCase() default UpperLowerCase.UNCHANGED;
      Class<? extends ResourceConverter>[] converter() default { };
    }
#### Nachträgliche Änderung und die Sinnhaftigkeit von Standardwerten Annotationstypen können ebenso wenig einfach geändert werden wie Schnittstellendeklarationen. Wird eine Methode nie über den Basistyp einer Schnittstelle aufgerufen, sondern die Schnittstelle lediglich implementiert, so kann diese ungenutzte Operation im Prinzip gelöscht werden[111](Es sei denn, es wird seit Java 6 die Annotation @Override für die implementierten Methoden verwendet.). Bei Annotationen ist das genauso: Wenn ein Annotationselement mit einem Standardwert belegt ist, und es nie genutzt wird, kann es gelöscht werden. Aber hier gilt analog zu den Schnittstellen: Gibt es eine dynamische Bindung über eine Schnittstelle und werden die Operationen entfernt, so gibt es genauso einen Compilerfehler, wie wenn es einen Zugriff auf ein Annotationselement gibt, und es dann gelöscht wird. Auch das Ändern von Elementtypen führt im Allgemeinen zu Compilerfehlern, denn wenn aus einem int plötzlich ein String wird, fehlen Anführungszeichen. Standardwerte sind für Annotationen ein sehr wichtiges Instrument, um neue Annotationselemente schmerzfrei einzuführen. Werden neue Elemente in bestehende Annotationstypen eingefügt, dann müssten alle existierenden konkreten Annotationen das neue Element setzen, was eine sehr große Änderung ist, vergleichbar einer neuen Operation in einer Schnittstelle. Anders als bei Schnittstellen lösen Default-Werte das Problem, da auf diese Weise für das neue Element immer gleich ein Wert vorhanden ist, der, sofern erwünscht, neu belegt werden kann. Ohne Probleme ist es möglich, einen Default-Wert hinzuzunehmen, während das Entfernen von Standardwerten wiederum kritisch ist. ### 18.5.8 Annotieren von Annotationstypen Von den in Java 5 eingeführten Annotationen haben wir die drei Typen aus dem Paket java.lang schon kennengelernt. Die restlichen vier Annotationen aus dem Paket java.lang.annotation dienen dazu, Annotationstypen zu annotieren. In diesem Fall wird von Meta-Annotationen gesprochen.

Tabelle 18.2: Meta-Annotationen

Annotation | Beschreibung
@Target | Was lässt sich annotieren? Klasse, Methode ...?
@Retention | Wo ist die Annotation sichtbar? Nur für den Compiler oder auch für die Laufzeitumgebung?
@Documented | Zeigt den Wunsch an, die Annotation in der Dokumentation zu erwähnen.
@Inherited | Macht deutlich, dass ein annotiertes Element auch in der Unterklasse annotiert ist.
#### @Target Die Annotation @Target beschreibt, wo eine Annotation angeheftet werden kann. Ist kein ausdrückliches @Target gewählt, gilt es für alle Elemente. Die Aufzählung java.lang.annotation.ElementType deklariert die folgenden Ziele:

Tabelle 18.3: ElementType bestimmt Orte, an denen Annotationen erlaubt sind.

ElementType | Erlaubt Annotationen ...
ANNOTATION_TYPE | an anderen Annotationstypen, was @Target(ANNOTATION_TYPE) somit zu einer Meta-Annotation macht.
TYPE | an allen Typdeklarationen, also Klassen, Schnittstellen, Aufzählungen.
CONSTRUCTOR | an Konstruktoren.
METHOD | an statischen und nicht-statischen Methoden.
FIELD | an statischen Variablen und Objekt-Variablen.
PARAMETER | an Parametervariablen.
LOCAL_VARIABLE | an lokalen Variablen.
PACKAGE | an package-Deklarationen.
Soll eine Annotation etwa vor beliebigen Typen, Methoden, Paketen und Konstruktoren erlaubt sein, so setzen wir Folgendes an die Deklaration der Annotation:
    @Target( { TYPE, METHOD, CONSTRUCTOR, PACKAGE } )
    public @interface ...
Unsere eigenen drei Annotationstypen sind nur für Attribute sinnvoll. So nutzen wir FIELD, was hier an CurrentDateResource gezeigt wird:
    @Target( java.lang.annotation.ElementType.FIELD )
    public @interface CurrentDateResource { }
Hinweis
Soll statt ElementType.FIELD einfach nur FIELD verwendet werden, so muss FIELD entsprechend aus ElementType statisch eingebunden werden. Damit ist folgender Programmcode eine Alternative:
    import static java.lang.annotation.ElementType.*;
    import java.lang.annotation.Target;

    @Target( FIELD )
    public @interface CurrentDateResource { }
Mit ElementType.TYPE ist die Annotation vor allen Typen – Klassen, Schnittstellen, Annotationen, Enums – erlaubt. Eine Einschränkung, etwa nur auf Klassen, ist nicht möglich. Interessant ist die Tatsache, dass eine Unterteilung für Methoden und Konstruktoren möglich ist und dass sogar lokale Variablen annotiert werden können.
Beispiel
Beim existierenden Annotationstyp @Override ist die Annotation @Target schön zu erkennen:
    @Target( value = METHOD )
    public @interface Override
Die Idee der Meta-Annotation: Es gibt nur überschriebene Methoden.
Annotationen für Pakete sind speziell, weil sich die Frage stellt, wo hier die Metadaten über ein Paket stehen sollen. Eine Klasse selbst wird ja einem Paket zugeordnet – sollte das heißen, in irgendeiner wahllosen Typdeklaration stehen dann an der package-Deklaration die Meta-Annotationen für das Paket? Nein, denn dann würde zum einen die Annotation bei vielen Typen vielleicht nie mehr wiedergefunden, und zum anderen gäbe es bestimmt Konflikte, wenn aus Versehen an zwei Typen widersprüchliche Annotationen an der package-Deklaration stünden. Java wählt eine andere Lösung. Es muss eine Datei mit dem Namen package-info.java im jeweiligen Paket stehen, und dort darf die package-Deklaration annotiert sein. Da der Dateiname schon kein Klassenname sein kann (Minuszeichen sind nicht erlaubt), wird die Datei auch keine Typdeklaration enthalten, aber der Compiler erzeugt natürlich eine .class-Datei für die Metadaten des Pakets. Kommentare sind selbstverständlich erlaubt, und die Datei wurde auch schon vor Java 5 für die API-Dokumentation eines Pakets verwendet. Dazu ein Beispiel. Ein neuer Annotationstyp AutomaticUmlDiagram soll deklariert werden, und er soll nur an Paketen gültig sein: Listing 18.22: com/tutego/insel/annotation/AutomaticUmlDiagram.java
    package com.tutego.insel.annotation;
    import java.lang.annotation.*;
    @Target( value = ElementType.PACKAGE  )
    public @interface AutomaticUmlDiagram {}
Das Paket com.tutego.insel.annotation soll nun mit AutomaticUmlDiagram annotiert werden: Listing 18.23: com/tutego/insel/annotation/package-info.java
    @AutomaticUmlDiagram
    package com.tutego.insel.annotation;
Die Datei package-info.java ist schlank, wird aber in der Regel größer sein, da sie das JavaDoc des Pakets enthält. #### @Retention Die Annotation @Retention steuert, wer die Annotation sehen kann. Es gibt drei Typen, die in der Aufzählung java.lang.annotation.RetentionPolicy genannt sind: SOURCE: Nützlich für Tools, die den Quellcode analysieren, aber die Annotationen werden vom Compiler verworfen, sodass sie nicht den Weg in den Bytecode finden. CLASS: Die Annotationen speichert der Compiler in der Klassendatei, aber sie werden nicht in die Laufzeitumgebung gebracht. RUNTIME: Die Annotationen werden in der Klassendatei gespeichert und sind zur Laufzeit in der JVM verfügbar. Die Unterscheidung haben die Java-Designer vorgesehen, da nicht automatisch jede Annotation zur Laufzeit verfügbar ist (eine Begründung: andernfalls würde es den Ressourcenverbrauch erhöhen). Der Standard ist RetentionPolicy.CLASS.
Beispiel
Der Annotationstyp @Deprecated ist nur für den Compiler und nicht für die Laufzeit von Interesse:
    @Retention( value = SOURCE )
    public @interface Deprecated
Ist ein Element mit @Target annotiert, so soll diese Information auch zur Laufzeit vorliegen:
    @Retention( value = RUNTIME )
    @Target( value = ANNOTATION_TYPE )
    public @interface Target
Das Beispiel zeigt, dass die Anwendung auch rekursiv sein kann (natürlich auch indirekt rekursiv, denn nicht nur @Retention annotiert @Target, auch @Target annotiert @Retention).
Für den Zugriff auf die Annotationen gibt es dann, je nach Retention-Typ, unterschiedliche Varianten. Im Fall von Source ist es ein Tool, das auf Textebene arbeitet, also etwa ein Compiler oder ein statisches Analysetool, das Quellcode analysiert. Sind die Annotationen im Bytecode abgelegt, so lassen sie sich über ein Werkzeug beziehungsweise eine Bibliothek auslesen. Zwei Wege sind möglich: zunächst über die Pluggable Annotation Processing API und dann über rohe Tools, die direkt auf der Ebene vom Bytecode arbeiten. Im ersten Fall gibt es eine eigene API, die das Erfragen einfach macht. Die zweite Lösung sind Bytecode-Bibliotheken, wie etwa ASM (unter http://asm.ow2.org/), die alles auslesen können, was in der Klassendatei steht, also auch die Annotationen. Sie sind aber proprietär und nicht einfach zu nutzen. Die dritte Variante ist einfach, da hier Reflection eine Möglichkeit bietet. Das schauen wir uns gleich im Anschluss in Abschnitt 18.5.10, »Annotierte Elemente auslesen«, an. #### @Documented Die Annotation @Documented zeigt an, dass die Annotation in der API-Dokumentation genannt werden soll. Alle Standard-Annotationen von Java werden so angezeigt, auch @Documented selbst. In der API-Dokumentation ist für die Annotationen ein neues Segment vorgesehen.
Beispiel
@Documented ist selbst @Documented:
    @Documented
    @Target( value = ANNOTATION_TYPE )
    public @interface Documented
### 18.5.9 Deklarationen für unsere Ressourcen-Annotationen Da unsere drei Annotationen zur Laufzeit ausgelesen werden sollen, muss die @Retention mit RetentionPolicy.RUNTIME gesetzt sein. Damit sind unsere Annotationstypen vollständig, und der Quellcode soll an dieser Stelle aufgeführt werden. Der einfachste Annotationstyp war CurrentDateResource: Listing 18.24: com/tutego/insel/annotation/CurrentDateResource.java, CurrentDateResource
    @Documented
    @Target( ElementType.FIELD )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface CurrentDateResource { }
Der Annotationstyp ListOfFilesResource erwartet eine Pfadangabe, ist aber nicht deutlich komplexer als CurrentDateResource: Listing 18.25: com/tutego/insel/annotation/ListOfFilesResource.java, ListOfFilesResource
    @Documented
    @Target( ElementType.FIELD )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface ListOfFilesResource
    {
      String value();
    }
Und zu guter Letzt: Der Annotationstyp UrlResource hat am meisten zu bieten. Doch beginnen wir zunächst mit der Deklaration der Schnittstelle für die Konverter: Listing 18.26: com/tutego/insel/annotation/ResourceConverter.java, ResourceConverter
    public interface ResourceConverter
    {
      String convert( String input );
    }
Zwei Implementierungen sollen für das Beispiel genügen: Listing 18.27: com/tutego/insel/annotation/SortConverter.java, SortConverter
    public class RemoveNoWordCharactersConverter implements ResourceConverter
    {
      @Override public String convert( String input )
      {
        return input.replaceAll( "\\W", "" );
      }
    }
Listing 18.28: com/tutego/insel/annotation/SortConverter.java, SortConverter
    public class SortConverter implements ResourceConverter
    {
      @Override public String convert( String input )
      {
        char[] chars = input.toCharArray();
        Arrays.sort( chars );
        return new String( chars );
      }
    }
Damit kann dann der letzte Annotationstyp übersetzt werden: Listing 18.29: com/tutego/insel/annotation/UrlResource.java, UrlResource
    @Documented
    @Target( ElementType.FIELD )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface UrlResource
    {
      enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }

      String value();
      boolean trim() default false;
      UpperLowerCase upperLowerCase() default UpperLowerCase.UNCHANGED;
      Class<? extends ResourceConverter>[] converter() default { };
    }
### 18.5.10 Annotierte Elemente auslesen Ob eine Klasse annotiert ist, erfragt ganz einfach die Methode isAnnotationPresent() auf dem Class-Objekt:
    println( String.class.isAnnotationPresent( Deprecated.class ) );                  // false
    println( StringBufferInputStream.class.isAnnotationPresent( Deprecated.class ) ); // true
Da unterschiedliche Dinge annotierbar sind, schreibt eine Schnittstelle AnnotatedElement für die Klassen Class, Constructor, Field, Method, Package und AccessibleObject folgende Operationen vor:
    interface java.lang.reflect.AnnotatedElement
<T extends Annotation> T getAnnotation(Class<T> annotationType) Liefert die Annotation für einen bestimmten Typ. Ist sie nicht vorhanden, dann ist die Rückgabe null. Der generische Typ ist bei der Rückgabe hilfreich. Denn das Argument ist ein Class-Objekt, das den Annotationstyp repräsentiert. Die Rückgabe ist genau die konkrete Annotation für das annotierte Element. boolean isAnnotationPresent(Class<? extends Annotation> annotationType) Gibt es die angegebene Annotation? Annotation[] getAnnotations() Liefert die an dem Element festgemachten Annotationen. Gibt es keine Annotation, ist das Feld leer. Die Methode liefert auch Annotationen, die aus den Oberklassen kommen. Annotation[] getDeclaredAnnotations() Liefert die Annotationen, die exakt an diesem Element festgemacht sind. Um die Annotationen etwa von Variablen oder Methoden zu erfragen, ist ein wenig Reflection-Wissen nötig. Ist obj ein Objekt, so findet folgende Schleife alle mit CurrentDateResource annotierten Objektvariablen und gibt eine Meldung aus:
    for ( Field field : obj.getFields() )
      if ( field.isAnnotationPresent( CurrentDateResource.class ) )
        System.out.println( "CurrentDateResource gesetzt" );
### 18.5.11 Auf die Annotationsattribute zugreifen Um auf die einzelnen Attribute einer Annotation zuzugreifen, müssen wir etwas mehr über die Umsetzung einer Annotation von Compiler und der JVM wissen. Übersetzt der Compiler einen Annotationstyp, generiert er daraus eine Schnittstelle.
Beispiel
Für den Annotationstyp ListOfFilesResource generiert der Compiler:
    import java.lang.annotation.Annotation;

    public interface ListOfFilesResource extends Annotation
    {
        public abstract String value();
    }
Rufen wir auf einem AnnotatedElement, etwa Field, eine Methode wie getAnnotation() auf, bekommen wir ein Objekt, das Zugriff auf unsere Element-Werte-Paare liefert. Denn zur Laufzeit werden über java.lang.reflect.Proxy Objekte gebaut, die unsere Schnittstelle – das ist ListOfFilesResource – implementiert und so die Methode value() anbietet.
Hinweis
Die Annotation ist zur Laufzeit ein Proxy-Objekt, und daher kann der Annotationstyp keine eigene Klasse erweitern und auch keine anderen eigenen Schnittstellen implementieren. Ein Annotationstyp kann auch keine anderen Annotationstypen erweitern. Es könnte eine eigene Klasse zwar die Schnittstelle java.lang.annotation.Annotation implementieren, doch entsteht dadurch keine echte Annotation, was den Versuch sinnlos macht.
Testen wir die Möglichkeit, indem wir zwei annotierte Variablen in eine Klasse setzen und dann per Reflection über alle Variablen laufen und alle Annotationen erfragen lassen: Listing 18.30: com/tutego/insel/annotation/GetTheUrlResourceValues.java, GetTheUrlResourceValues
    public class GetTheUrlResourceValues
    {
      @UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
                    upperLowerCase = UpperLowerCase.UPPERCASE, trim = true,
                    converter = { RemoveNoWordCharactersConverter.class, SortConverter.class } )
      public String testFile;

      @XmlValue @Deprecated
      public String xmlValue;

      public static void main( String[] args ) throws Exception
      {
        for ( Field field : GetTheUrlResourceValues.class.getFields() )
          for ( Annotation a : field.getAnnotations() )
            System.out.println( a );
      }
    }
Die Ausgabe zeigt drei Annotationen:
    @com.tutego.insel.annotation.UrlResource(converter=[class com.tutego.insel.annotation.
    RemoveNoWordCharactersConverter, class com.tutego.insel.annotation.SortConverter],
    trim=true, upperLowerCase=UPPERCASE, value=http://tutego.de/aufgaben/bond.txt)
    @javax.xml.bind.annotation.XmlValue()
    @java.lang.Deprecated()
Die Default-Werte werden zur Laufzeit gesetzt. ### 18.5.12 Komplettbeispiel zum Initialisieren von Ressourcen Zusammenfassend können wir jetzt eine Klasse vorstellen, die tatsächlich die mit den Ressourcen-Annotationen versehenen Variablen mit sinnvollem Inhalt füllt. Zunächst betrachten wir ein Beispiel, das die Nutzung einer solchen Klasse aufzeigt. Die Klasse Resources bildet den Rahmen für Objekte, die automatisch aufgebaut und korrekt initialisiert werden sollen: Listing 18.31: com/tutego/insel/annotation/AnnotatedResourceExample.java, Resources
    class Resources
    {
      @CurrentDateResource()
      public Date now;

      @ListOfFilesResource( value = "c:/" )
      public String[] files;

      @UrlResource( "http://tutego.de/aufgaben/bond.txt" )
      public String testFile;
    }
Einer zweiten Klasse geben wir ein main() und setzen dort die Aufforderung, ein Objekt vom Typ Resources anzulegen und zu initialisieren: Listing 18.32: com/tutego/insel/annotation/AnnotatedResourceExample.java, AnnotatedResourceExample
    public class AnnotatedResourceExample
    {
      public static void main( String[] args )
      {
        Resources resources =
          ResourceReader.getInitializedResourcesFor( Resources.class );
        System.out.println( resources.now );
        System.out.println( Arrays.toString( resources.files ) );
        System.out.println( resources.testFile );
      }
    }
Kommen wir zum Herzen, der Klasse ResourceReader: Listing 18.33: com/tutego/insel/annotation/ResourceReader.java, ResourceReader
    package com.tutego.insel.annotation;

    import java.io.File;
    import java.lang.reflect.Field;
    import java.net.URL;
    import java.util.Date;
    import java.util.Scanner;

    public class ResourceReader
    {
      public static <T> T getInitializedResourcesFor( Class<T> ressources )
      {
        try
        {
          T newInstance = ressources.newInstance();

          for ( Field field : ressources.getFields() )
          {
            if ( field.isAnnotationPresent( CurrentDateResource.class ) )
              field.set( newInstance, new Date() );

            else if ( field.isAnnotationPresent( ListOfFilesResource.class ) )
              field.set( newInstance, new File(field.getAnnotation(
                ListOfFilesResource.class ).value().toString()).list() );
            else if ( field.isAnnotationPresent( UrlResource.class ) )
            {
              String url = field.getAnnotation( UrlResource.class ).value();
              String content = new Scanner( new URL(url).openStream() )
                                     .useDelimiter( "\\z" ).next();

              if ( field.getAnnotation( UrlResource.class ).trim() )
                content = content.trim();

              switch ( field.getAnnotation( UrlResource.class ).upperLowerCase() )
              {
                case UPPERCASE: content = content.toUpperCase(); break;
                case LOWERCASE: content = content.toLowerCase(); break;
                default: // Nichts zu tun
              }
              Class<? extends ResourceConverter>[] converterClasses =
                field.getAnnotation( UrlResource.class ).converter();
              for ( Class<? extends ResourceConverter>
                    converterClass : converterClasses )
                content = converterClass.newInstance().convert( content );

              field.set( newInstance, content );
            }
          }

          return newInstance;
        }
        catch ( Exception e )
        {
          return null;
        }
      }
    }
An den folgenden Anweisungen ist das Prinzip gut ablesbar:
    T newInstance = ressources.newInstance();
    for ( Field field : ressources.getFields() )
      if ( field.isAnnotationPresent( CurrentDateResource.class ) )
        field.set( newInstance, new Date() );
Zunächst wird ein neues Exemplar, ein Behälter, aufgebaut. Dann läuft eine Schleife über alle Variablen. Gibt es zum Beispiel die Annotation CurrentDateResource an einer Variablen, so wird ein Date-Objekt aufgebaut und mit set() die Variable mit dem Datum initialisiert. ### 18.5.13 Mögliche Nachteile von Annotationen Annotationen sind eine gewaltige Neuerung und sicherlich die wichtigste seit vielen Java-Jahren. Auch wenn die Generics auf den ersten Blick bedeutsam erscheinen, sind die Annotationen ein ganz neuer Schritt in die deklarative Programmierung, wie sie Frameworks schon heute aufzeigen. Völlig problemlos sind Annotationen allerdings nicht, und so müssen wir etwas Wasser in den Wein gießen: Die Annotationen sind stark mit dem Quellcode verbunden, können also auch nur dort geändert werden. Ist der Original-Quellcode nicht verfügbar, etwa weil der Auftraggeber ihn geschlossen hält, ist eine Änderung der Werte nahezu unmöglich. Wenn Annotationen allerdings nach der Übersetzung nicht mehr geändert werden können, stellt das bei externen Konfigurationsdateien kein Problem dar. Externe Konfigurationsdateien können ebenso den Vorteil bieten, dass die relevanten Informationen auf einen Blick erfassbar sind und sich mitunter nicht redundant auf unterschiedliche Java-Klassen verteilen. Klassen mit Annotationen sind invasiv und binden auch die Implementierungen an einen gewissen Typ, wie es Schnittstellen tun. Sind die Annotationstypen nicht im Klassenpfad, kommt es zu einem Compilerfehler. Bisher gibt es keine Vererbung von Annotationen: Ein Annotationstyp kann keinen anderen Annotationstyp erweitern. Die bei den Annotationen gesetzten Werte lassen sich zur Laufzeit erfragen, aber nicht modifizieren. Warum werden Annotationen mit @interface deklariert, einer Schreibweise, die in Java sonst völlig unbekannt ist? Ein Problem gibt es allerdings nur bei finalen statischen Variablen (Konstanten), das bei den Default-Werten der Annotationen nicht vorkommt: Weil die Default-Werte zur Laufzeit gesetzt werden, lassen sie sich in der Deklaration vom Annotationstyp leicht ändern, und eine Neuübersetzung des Projekts kann somit unterbleiben. Zur Ehrenrettung sei erwähnt, dass moderne Frameworks wie JPA oder JSF 2 aus dem Java EE-Standard immer noch den Einsatz von XML vorsehen. So lässt sich auf Annotationen verzichten bzw. XML einsetzen, sodass Zuweisungen aus den Annotationen überschrieben werden können. ### Ihr Kommentar Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück | <top> | vor >>
Zum Katalog
Java 7 – Mehr als eine Insel Jetzt bestellen
Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen? Ihre Meinung
Buchempfehlungen
| Java und XML
| Einstieg in Eclipse 3.7
| Android 3
| NetBeans Platform 7
| Java ist auch eine Insel
| Apps entwickeln für Android 4
| Java 7
Shopping
Versandkostenfrei bestellen in Deutschland und Österreich Info

Copyright © Galileo Press 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

[Galileo Computing]
Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de