Annotations mit Java

Autor:
Ralf Rapude

1. Einleitung
Java 5 macht es leicht, eigene Metainformationen (Annotations sind nichts anderes als Metainformationen) in den Quellext einzufügen, die dann zur Laufzeit oder während des Kompilierens ausgewertet werden können. Anwendung finden Annotations z.B. bei der Erstellung von Enterprise Beans, bei denen sie unter anderem die Bereitstellung im Application Server beschreiben und damit umfangreiche Deploymentdeskriptoren überflüssig machen, wie sie noch bei EJB 2.1 notwendig waren.
Annotations werden aber nicht nur für Enterprise Anwendungen genutzt, sondern auch bei der Entwicklung mit Java SE. Hier finden wir sie z.B. beim Überschreiben von Methoden, die aus Oberklassen geerbt wurden (@Override) oder auch die "@Deprecated" Annotation, wenn Methoden veraltet sind und irgendwann ersetzt werden sollen.

top

2. Systemvoraussetzungen
Um dieses Tutorial nachzuvollziehen braucht ihr das Sun JDK und, wenn ihr es noch ein wenig komfortabler haben wollt, Eclipse als IDE, weil ihr dann die Möglichkeit habt das Tutorial herunterzuladen und einfach in Eclipse zu importieren. Alles andere sollte dann out-of-the-box laufen.

  • Java SDK ( zur Zeit aktuell 1.5.0_09)
  • Eclipse (aktuell ist zur Zeit 3.2.1)

Das Tutorial könnt ihr hier runterladen.

top

3. Vorbereitungen
Nach dem herunterladen des Tutorials zunächst das zip entpacken und, falls Eclipse genutzt wird, als Projekt importieren. Dieser Import ist einfach zu bewerkstelligen, wenn im Navigator oder dem Package Explorer über die rechte Maustaste der der Menüpunkt "import" ausgewählt wird. Wenn ein anderer Editor genutzt wird, ist es wichtig, dass noch das junit-4.0.jar (ist Teil des zips) in den Classpath des Projektes eingebunden wird. Danach das Projekt kompilieren und jetzt sollte folgende Verzeichnisstruktur sichtbar sein:

Verzeichnisstruktur nach dem entpacken

Um einen ersten Test zu machen, ob alles funktioniert, öffnet ihr am besten im Editor die AnnotationTest.java und führt die Klasse als JUnit Test aus. Um das zu machen, klickt ihr einfach in den Editor und öffnet das Kontextmenü mit der rechten Maustaste. Unter dem Punkt "Run as" findet ihr jetzt die Möglichkeit "JUnit Test". Diesen Punkt wählt ihr aus und dann sollte die JUnit View erscheinen, die mit einem grünen Balken darauf hinweist, dass alle Tests erfolgreich waren.

top


4. Projektstruktur
Ihr findet drei Klassen im Source Folder des Projektes:

  1. Das Interface AnnotationIFace.java: Dieses Interface beschreibt die Annotation, die wir verwenden wollen.
  2. Die AnnotationClass.java: Diese Datei enthält die Implementierung unsererer Metainformation.
  3. Die AnnotationTest.java: Diese Klasse testest unser Interface und macht die Auswertung der Annotation während der Laufzeit deutlich.

top

5. Die Applikation
1. AnnotationIFace.java

...
@Retention
(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface AnnotationIFace {
   String whatString() default "huhu";
}
...

Das Interface, dass unsere Annotation beschreibt, beginnt bereits mit einer Metainformation. "RetentionPolicy.RUNTIME" ist Teil der Enumeration java.lang.annotation.RetentionPolicy und beschreibt die spätere Verwendung der Annotation. In diesem Fall bedeutet sie, das wir die Annotation während der Laufzeit des Programms auswerten möchten. Andere mögliche Angaben wären hier:

  1. RuntimePolicy.CLASS: Die Annotaion ist nur während des Compiles verfügbar, aber nicht mehr zur Laufzeit
  2. RuntimePolicy.SOURCE: Die Annotation wird vor dem Kompilieren aus dem Sourcecode entfernt und kann damit ebenso nicht mehr während der Laufzeit genutzt werden.

Die zweite Annotation ist durch @Target gekennzeichnet. Hier kann angegeben werden, auf welches Element einer Klasse sich die Annotation bezieht. In unserem Fall ist sie für Felder vorgesehen, aber es wäre auch möglich, sie kommasepariert für andere Teile verfügbar zu machen. Beispielsweise ElementType.METHOD bei einer Methode oder ElementType.CONSTRUCTOR, wenn sie für einen Konstruktor nutzbar sein soll. Weitere mögliche Verwendungsarten und Infos sind in der Api zu finden und ElementType ist Teil einer Enumeration aus der Klasse
java.lang.annotation.ElementType.

Spannend wird es dann nochmal beim Namen des Interfaces. Hier wird nämlich vor dem Namen durch das Schlüsselwort @interface kenntlich gemacht, das es sich nicht um ein normales Interface handelt, sondern um ein Interface, das als Annotation zur Verfügung stehen soll.
Darunter geht es dann annähernd normal und wie aus jedem anderen Interface bekannt weiter. Wir beschreiben eine Methode "whatString()", die ein Stringobjekt zurückgibt, weisen diesem String jedoch durch das Schlüsselwort "default" einen Defaultwert zu, d.h. wenn bei Gebrauch der Annotation keine Wertzuweisung erfolgt, ist "huhu" der String, der in diesem Fall zurückgegeben wird.

top

2. AnnotationClass.java
Hier sind eigentlich nur zwei Neuerungen zu finden , die diese Klasse von einer der üblichen Klassen aus Java 1.4 unterscheiden.

@AnnotationIFace(whatString = "str1")
public String str1 = "String";

@AnnotationIFace
public String str2 = "default";

Auffällig ist , das wir den Namen für das von uns geschriebene Interface wiederfinden. Durch die Annotation @AnnotationIFace haben wir kenntlich gemacht, dass wir die Annotation nutzen wollen. Durch die Klammern weisen wir ihr jetzt den String "str1" zu.
Im Gegensatz dazu erfolgt beim zweiten Stringobjekt str2 keine Zuweisung. Das geschieht, um zu beweisen, das in einem solchen Fall der Defaultwert der in der AnnotationIFace.java angegeben wurde, auch verwandt wird.

top

3. AnnotationTest.java
Hier erfolgt die Auflösung, wie wir während der Laufzeit die Annotation auswerten können und gleichzeitig stellen wir sicher, das der Code, der geschrieben wurde, auch funktioniert.

  1. public void readAnnotationOne() throws Exception {
  2.     AnnotationClass impl = new AnnotationClass();
  3.     Class<?> implCl = impl.getClass();
  4.     Field field = implCl.getDeclaredField("str1");
  5.     AnnotationIFace anno = field.getAnnotation(AnnotationIFace.class);

  6.     assertEquals("str1", anno.whatString());
  7. }

Zunächst erzeugen wir ein neues Objekt vom Typ "AnnotationClass" und holen uns per Reflection in Zeile 3 das zugehörige Klassenobjekt.
In Zeile 4 greifen wir dann über das Classobjekt auf das Feld "str1" zu, um dann über das Fieldobjekt und die Methode "getAnnotation(AnnotationIFace.class)" in Zeile 5 an die zugehörige Annotation des Feldes zu kommen. Die Auswertung erfolgt dann in der Zeile 6 über den Aufruf der Methode "assertEquals(String, String)" (diese Methode wird über den statischen import der Klasse org.junit.Assert geerbt), in der überprüft wird, ob der Wert, den wir erwarten, mit dem übereinstimmt, der in der Klasse AnnotationClass.java zugewiesen wurde. Sicher wird es keinen mehr überraschen, dass das der Fall ist und die Werte übereinstimmen :o)

Interessant wird es dann nochmal im zweiten Test. Hier ist das Vorgehen das gleiche, jedoch haben wir dieser Annotation keine Wert zugewiesen. In diesem Fall kommt der Defaultwert des Interfaces zum tragen und deshalb wird auch der zweite Unittest erfolgreich ausgeführt.

top