Kolumne EnterpriseTales

Java 8 und Enterprise – Teil 4: Repeatable Annotations in Java EE

Arne Limburg, Lars Röwekamp

©shutterstock.com/vicgmyr

Unsere kleine Serie zum Thema Java 8 und Enterprise geht nun schon in die vierte Runde (hier geht es zu Teil 1, Teil 2 und Teil 3). Heute wollen wir das Java-8-Thema der „Repeatable Annotations“ näher betrachten und dessen Bedeutung für Java EE unter die Lupe nehmen.

Bis Java 7 war es nicht möglich, ein und dieselbe Annotation mehrfach an eine Klasse zu setzen, selbst wenn die Annotation mit verschiedenen Parametern gefüttert wurde.

Seit Java 8 hat sich das geändert. Gewisse Annotationen lassen sich mehrfach an Klassen, Felder oder Methoden setzen. Möglich macht das die Annotation @Repeatable und ein kleiner Compiler-Trick, der in Java 8 eingebaut wurde. Mit diesem Trick übernimmt in Java 8 der Compiler das, was in Java 7 der Entwickler von Hand machen muss, wie wir später sehen werden.

Mehrfach-Annotationen in Java EE

Wenn man in Java 7 oder früheren Versionen eine Annotation mehrfach an eine Klasse schreiben will, so benötigt man immer eine Wrapper-Annotation. Möchte man z. B. eine JPA-Entity mit mehr als einer @NamedQuery versehen, so muss man dazu die Wrapper-Annotation @NamedQueries verwenden. Diese kapselt ein Array von @NamedQuery-Annotations, wodurch es möglich wird, mehr als eine davon an eine Entity zu schreiben. Gleiches gilt für weitere Annotations im JPA-Standard.

Auch im BeanValidation-Standard werden solche Annotationen zur Verfügung gestellt. BeanValidation-Implementierungen erkennen die Annotationen sogar automatisch. Dadurch ist es möglich, auch für eigene Constraint-Annotations solche Wrapper-Annotations zu schreiben. Die BeanValidation-Spec schlägt dazu ein eigenes Pattern vor: Für jede Constraint-Annotation wird eine Annotation namens List als innere Klasse definiert, die als Wrapper-Annotation fungiert. Bei den Constraint-Annotations, die von der Spezifikation mitgeliefert werden (wie @NotNull, @Min, @Max usw.), wurde dieses Pattern auch angewendet. Bei selbstdefinierten Constraints empfiehlt sich die Verwendung des Patterns ebenfalls (Listing 1).

@Constraint(validatedBy = ...)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}
@Retention(RUNTIME)
@Repeatable(MyCustomConstraint.List.class)
public @interface MyCustomConstraint {
    String myCustomValue();

    public @interface List {
        MyCustomConstraint[] value();
    }
}


Repeatable Annotations in Java 8

Was haben diese Wrapper-Annotationen aber mit den Repeatable Annotationen in Java 8 zu tun? Sehr viel, denn der oben bereits erwähnte kleine Compiler-Trick basiert darauf, dass der Compiler jedes Mal, wenn er eine mehrfach verwendete Annotation findet, diese Liste mit der zugehörigen Wrapper-Annotation wrappt. Verwendet man im Beispiel von Listing 1 also @MyCustomConstraint mehrfach an einem Feld, wird der Compiler daraus @MyCustomConstraint.List({@MyCustomConstraint, …}) machen. Damit er in der Lage ist, das zu tun, muss eine Annotation, die mehrfach verwendet werden soll, mit der Annotation @Repeatable versehen werden, wobei der Repeatable-Annotation als Parameter der Typ der Wrapper-Annotation mitgegeben werden muss (Listing 1).

Sind also Repeatable Annotations in Java EE 7 möglich? Für die von den Standards definierten Annotations (@NamedQuery, @NotNull, …) ist die Antwort leider nein. Das liegt daran, dass sie nicht mit der Annotation @Repeatable versehen sind, was auch nicht verwundert, weil es @Repeatable ja erst seit Java 8 gibt, Java EE 7 aber auf Java 7 basiert.

Für selbst definierte Annotationen sieht die Welt anders aus. Wie gezeigt, ist es für BeanValidation-Constraints möglich, sie als Repeatable Annotations zu entwickeln.

Nahezu alle anderen Specs bieten prinzipiell keine Möglichkeit, die mitgelieferten Annotations zu erweitern, was die Verwendung von Repeatable Annotations unmöglich macht. Einzige weitere Ausnahme ist die CDI-Spec. Leider funktionieren hier die Repeatable Annotations nicht out of the box. Eine kleine CDI-Extension hilft hier aber aus dem Dilemma. Will man z. B. ein und denselben Qualifier mehrfach an einer Klasse verwenden (z. B. die in einer früheren Kolumne bereits erläuterte @TenantId), so kann man sie als Repeatable Annotation schreiben, muss sich aber um die Verarbeitung der Wrapper-Annotation selbst kümmern. Im Wesentlichen geht es dabei darum, die Wrapper-Annotation zu erkennen und, wenn vorhanden, die in ihr enthaltenen Qualifier-Annotations direkt als Qualifier in den in CDI 1.1 neu hinzugekommenen BeanAttributes zurückzugeben. Hierzu werden die bestehenden BeanAttributes gewrappt und dabei die Qualifier ausgetauscht (Listing 2).

public <T> void applyTenantIds(@Observes ProcessBeanAttributes<T> pba) {
    BeanAttributes<T> attributes = pba.getBeanAttributes();
    final Set<Annotation> qualifiers = new HashSet<>();
    for (Annotation qualifier: attributes.getQualifiers()) {
        if (qualifier.annotationType().equals(TenantId.List.class)) {
            TenantId.List tenantIds = (TenantId.List)qualifier;
            qualifiers.addAll(Arrays.asList(tenantIds.value());
        } else {
            qualifiers.add(a);
        }
    }
    pba.setBeanAttributes(new BeanAttributes<T>() {
        public Set<Annotation> getQualifiers() {
            return qualifiers;
        }
        ...
    });
}


Fazit

Das Java-8-Feature der Repeatable Annotations kann mit den Standard-Annotations von Java EE 7 noch nicht verwendet werden, weil Java 7 die Zielplattform von Java EE 7 ist.

Hat man also eine Annotation aus einem der Java-EE-7-Standards in der Hand (wie @NamedQuery und @NotNull), wird es vorerst nicht möglich sein, diese mehrfach an eine Klasse, ein Feld oder eine Methode zu schreiben. Es ist aber davon auszugehen, dass in Java EE 8 eben diese und alle weiteren relevanten Annotations der einzelnen Specs mit der Annotation @Repeatable versehen werden, sodass sie ab Java EE 8 auch mehrfach eingesetzt werden können.

Für eigene Annotations gilt diese Einschränkung natürlich nicht. Wie gesehen, ist es mit Java 8 z. B. möglich eigene BeanValidation-Constraints oder CDI-Qualifier so zu schreiben, dass sie mehrfach an derselben Klasse eingesetzt werden können.

Während BeanValidation diese Annotations bereits automatisch erkennt, muss man bei CDI noch etwas Hand anlegen, bevor solche Annotations als Qualifier angesehen werden. Dies kann in Form einer CDI-Extension passieren, wie wir gezeigt haben. Es ist zu hoffen, dass ein solches Feature ab Java EE 8 auch in CDI einfließen wird, sodass die Extension überflüssig wird.

Aufmacherbild: March 8 symbol of red ribbon von Shutterstock / Urheberrecht: vicgmyr

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: