Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
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

Buch bestellen
Ihre Meinung?

Spacer
Java 7 - Mehr als eine Insel von Christian Ullenboom
Das Handbuch zu den Java SE-Bibliotheken
Buch: Java 7 - Mehr als eine Insel

Java 7 - Mehr als eine Insel
Rheinwerk Computing
1433 S., 2012, geb.
49,90 Euro, ISBN 978-3-8362-1507-7
Pfeil 18 Reflection und Annotationen
Pfeil 18.1 Metadaten
Pfeil 18.1.1 Metadaten durch JavaDoc-Tags
Pfeil 18.2 Metadaten der Klassen mit dem Class-Objekt
Pfeil 18.2.1 An ein Class-Objekt kommen
Pfeil 18.2.2 Was das Class-Objekt beschreibt *
Pfeil 18.2.3 Der Name der Klasse
Pfeil 18.2.4 instanceof mit Class-Objekten *
Pfeil 18.2.5 Oberklassen finden *
Pfeil 18.2.6 Implementierte Interfaces einer Klasse oder eines Interfaces *
Pfeil 18.2.7 Modifizierer und die Klasse Modifier *
Pfeil 18.2.8 Die Arbeit auf dem Feld *
Pfeil 18.3 Attribute, Methoden und Konstruktoren
Pfeil 18.3.1 Reflections – Gespür für die Attribute einer Klasse
Pfeil 18.3.2 Methoden einer Klasse erfragen
Pfeil 18.3.3 Properties einer Bean erfragen
Pfeil 18.3.4 Konstruktoren einer Klasse
Pfeil 18.3.5 Annotationen
Pfeil 18.4 Objekte erzeugen und manipulieren
Pfeil 18.4.1 Objekte erzeugen
Pfeil 18.4.2 Die Belegung der Variablen erfragen
Pfeil 18.4.3 Eine generische eigene toString()-Methode *
Pfeil 18.4.4 Variablen setzen
Pfeil 18.4.5 Bean-Zustände kopieren *
Pfeil 18.4.6 Private Attribute ändern
Pfeil 18.4.7 Methoden aufrufen
Pfeil 18.4.8 Statische Methoden aufrufen
Pfeil 18.4.9 Dynamische Methodenaufrufe bei festen Methoden beschleunigen *
Pfeil 18.5 Eigene Annotationstypen *
Pfeil 18.5.1 Annotationen zum Laden von Ressourcen
Pfeil 18.5.2 Neue Annotationen deklarieren
Pfeil 18.5.3 Annotationen mit genau einem Attribut
Pfeil 18.5.4 Element-Werte-Paare (Attribute) hinzufügen
Pfeil 18.5.5 Annotationsattribute vom Typ einer Aufzählung
Pfeil 18.5.6 Felder von Annotationsattributen
Pfeil 18.5.7 Vorbelegte Attribute
Pfeil 18.5.8 Annotieren von Annotationstypen
Pfeil 18.5.9 Deklarationen für unsere Ressourcen-Annotationen
Pfeil 18.5.10 Annotierte Elemente auslesen
Pfeil 18.5.11 Auf die Annotationsattribute zugreifen
Pfeil 18.5.12 Komplettbeispiel zum Initialisieren von Ressourcen
Pfeil 18.5.13 Mögliche Nachteile von Annotationen
Pfeil 18.6 Zum Weiterlesen

Rheinwerk Computing - Zum Seitenanfang

18.5 Eigene Annotationstypen *Zur nächsten Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

18.5.1 Annotationen zum Laden von RessourcenZur nächsten ÜberschriftZur vorigen Überschrift

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;

Rheinwerk Computing - Zum Seitenanfang

18.5.2 Neue Annotationen deklarierenZur nächsten ÜberschriftZur vorigen Überschrift

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;

Rheinwerk Computing - Zum Seitenanfang

18.5.3 Annotationen mit genau einem AttributZur nächsten ÜberschriftZur vorigen Überschrift

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; // Fehler 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; // Fehler Compilerfehler
@ListOfFilesResource( null ) String[] files; // Fehler Compilerfehler
@ListOfFilesResource( 1 ) String[] files; // Fehler Compilerfehler
@ListOfFilesResource( 'C' ) String[] files; // Fehler Compilerfehler
@ListOfFilesResource( "C:" + '/' ) String[] files; // OK


Rheinwerk Computing - Zum Seitenanfang

18.5.4 Element-Werte-Paare (Attribute) hinzufügenZur nächsten ÜberschriftZur vorigen Überschrift

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;

Rheinwerk Computing - Zum Seitenanfang

18.5.5 Annotationsattribute vom Typ einer AufzählungZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

18.5.6 Felder von AnnotationsattributenZur nächsten ÜberschriftZur vorigen Überschrift

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" )
} )


Rheinwerk Computing - Zum Seitenanfang

18.5.7 Vorbelegte AttributeZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

18.5.8 Annotieren von AnnotationstypenZur nächsten ÜberschriftZur vorigen Überschrift

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


Rheinwerk Computing - Zum Seitenanfang

18.5.9 Deklarationen für unsere Ressourcen-AnnotationenZur nächsten ÜberschriftZur vorigen Überschrift

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 { };
}

Rheinwerk Computing - Zum Seitenanfang

18.5.10 Annotierte Elemente auslesenZur nächsten ÜberschriftZur vorigen Überschrift

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" );

Rheinwerk Computing - Zum Seitenanfang

18.5.11 Auf die Annotationsattribute zugreifenZur nächsten ÜberschriftZur vorigen Überschrift

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.Zeilenumbruch
RemoveNoWordCharactersConverter, class com.tutego.insel.annotation.SortConverter], Zeilenumbruch
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.


Rheinwerk Computing - Zum Seitenanfang

18.5.12 Komplettbeispiel zum Initialisieren von RessourcenZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

18.5.13 Mögliche Nachteile von AnnotationenZur vorigen Überschrift

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
  Zum Katalog
Neuauflage: Java SE 8 Standard-Bibliothek
Neuauflage: Java SE 8 Standard-Bibliothek
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Einstieg in Eclipse






 Einstieg in Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de