15. November 2010 | 4 min lesezeit

Integration von Spring in CDI über eine CDI-Extension – Erster Teil

In meinem Beitrag zum Java-EE-Sonderheft des Java-Magazins in dem es um die Frage ging, ob man vor dem Hintergrund des Java EE 6 Standards bei Neuentwicklungen auf Spring oder auf Java EE setzen soll, habe ich auf ein bisher wenig beachtetes Feature des Standards hingewiesen: Java EE Extensions, genauer gesagt, CDI-Extensions. Während die letzten Jahre im Zeichen von Frameworks standen, die das Ziel hatten, Teile von Java EE (damals noch EJB 2.x) zu ersetzen (Hibernate, Spring, Seam), bieten CDI-Extensions nun die Möglichkeit, Frameworks zu schaffen, die Java EE ergänzen und erweitern. Ich habe sogar behauptet, dass die Integration von Spring in CDI in wenigen Hundert Zeilen Code möglich ist. In diesem Blog will ich zeigen, wie das geht.

Vorüberlegungen:

Sowohl eine CDI-Implementierung als auch Spring managen Beans. Es wird wohl nicht möglich sein, dieselbe Bean (also dasselbe Java-Objekt) sowohl von Spring als auch von der CDI-Implementierung managen zu lassen. Eine sinnvolle Integration muss dann aber sicherstellen, dass es möglich ist, sowohl Spring-Beans in CDI-Beans injecten zu lassen, als auch umgekehrt. Eine Integration von Spring in CDI wird also wohl zwei Teile umfassen:

  1. Man muss CDI die Spring-Beans „unterschieben“
  2. Man muss Spring die CDI-Beans „unterschieben“

Beides klingt wie eine interessante Herausforderung, da einige Kenntnis der Interna der beiden Lösungen vonnöten ist. Wenn CDI tatsächlich so leicht zu erweitern ist, sollte es möglich sein, den ersten Teil als CDI-Erweiterung umzusetzen. Was dazu zu tun ist, möchte ich im ersten Teil dieses Blogs zeigen:

Wie sieht eine CDI-Erweiterung aus? CDI-Erweiterungen werden über das Java Service-Provider-Interface angeboten und zwar unter dem Namen javax.enterprise.inject.spi.Extension, d.h. wir müssen im Ordner META-INF eine Datei namens javax.enterprise.inject.spi.Extension anlegen. Einziger Inhalt der Datei ist der voll qualifizierte Klassenname unserer Erweiterung, z.B. de.openknowledge.cdi.SpringCdiExtension. Über den java.util.ServiceLocator kann die CDI-Implementierung dann unsere Erweiterung zur Laufzeit instanziieren. Zu diesem Zweck muss unsere Klasse einen Default-Konstruktor haben. Eine weitere Bedingung, die die CDI-Spec an die Klasse stellt, ist, dass sie das Marker-Interface javax.enterprise.inject.spi.Extension implementiert.

Mit der Umsetzung dieser Bedingungen wäre die Extension zwar komplett, sie würde aber offensichtlich noch nichts tun. Wie also kann eine CDI-Extension mit dem CDI-Container in Interaktion treten? Die Antwort ist einfach: Sie kann auf Events des Containers hören. Damit eine CDI-Extension CDI-Events erhalten kann, muss sie eine Methode definieren, die bei Auftreten des Events aufgerufen werden kann. Wir wollen das CDI-Event AfterBeanDiscovery überwachen. Dieses tritt auf, wenn der Container die Erkennung seiner Beans abgeschlossen hat. Das Besondere an diesem Event ist, dass man über das Event weitere Beans beim CDI-Container registrieren kann und zwar über die Methode addBean(Bean bean). Die Metadaten für CDI stecken in Implementierungen des Interfaces javax.enterprise.inject.spi.Bean und Metadaten für Spring sind durch das Interface org.springframework.beans.factory.config.BeanDefinition repräsentiert.

Alles was wir also tun müssen, damit der CDI-Container die Spring-Beans verwenden kann, ist, das Bean-Interface von CDI zu implementieren für jede Spring-BeanDefinition addBean(…) auf dem AfterBeanDiscovery Event aufzurufen:

Damit kennt der CDI-Container alle Spring-Beans und sie stehen in CDI zur Dependency-Injection zur Verfügung. Leider liefert der CDI-Standard keine Implementierung des javax.enterprise.inject.spi.Bean-Interfaces mit, so dass wir eine eigene Implementierung schreiben müssen:

Die Implementierung ist recht trivial. Dennoch ein paar Bemerkungen: Zunächst ist die Implementierung von create und destroy interessant. Hier wird die Bean-Instanz aus dem Spring-Context geholt, bzw. zerstört. Die Erläuterung des Dependent-Scopes und der Implementierung von isAlternative() (die sich auf das CDI-Konzept der Alternatives bezieht) würde hier den Rahmen sprengen und ist vielleicht Gegenstand eines nächsten Blogs. Bleibt nur die Frage, wie man den doch recht umfangreichen Konstruktor korrekt zu befüllen hat. Bei den Parametern handelt es sich hauptsächlich um Metadaten, die in CDI durch Annotations repräsentiert werden. Für deren Erkennung bietet CDI ein paar schöne Methoden: BeanManager.createAnnotatedType(Class type), BeanManager.isQualifier(…) und beanManager.isStereotype(…). Den AnnotatedType kann man dann nach den benötigten Annotations durchsuchen:

Über zwei Zeilen muss ich noch ein paar Worte verlieren:

  1. Laut CDI-Spec hat jede Bean implizit die Qualifier @Any und @Default. Das wird von dem CDI-Container Weld (den ich zum Testen verwendet habe) so interpretiert, dass diese Qualifier immer enthalten sein müssen. Keine Ahnung ob z.B. OpenWebBeans (ein weiterer CDI-Container) diese beiden Annotations auch in der Liste benötigt… Da sie immer implizit da sind (also wahrscheinlich nie als echte Annotation an einer Bean stehen), könnte man sie meiner Meinung nach auch aus dieser Liste weglassen. Wie gesagt, Weld braucht sie.
  2. Was soll das für ein Konstrukt sein, was CDI da mitliefert AnnotationLiteral<Any>() {} ???

Nun ja, wir alle kennen Annotations und zwar als @Any z.B. Diejenigen, die schon mal eine eigene Annotation geschrieben haben, wissen auch, dass das sowas ähnliches wie ein Interface ist, d.h. es gibt auch ein zugehöriges class-Objekt. Wenn wir also (wie bei den Stereotypes) eine Annotation-Klasse benötigen, können wir einfach Any.class verwenden. Bei den Qualifiers benötigen wir aber Annotation-Instanzen, weil auch die Attribute relevant sein können. Normalerweise instantiiert die JVM die Annotation-Instanzen automatisch nach Bedarf.

Was kann man allerdings machen, wenn man Annotations von Hand instanziieren muss? Hierfür bietet CDI die Klasse AnnotationLiteral. Annotation-Instanzen implementieren immer das Interface java.lang.annotation.Annotation. Dies tut AnnotationLiteral auch und verhält sich auch sonst so wie eine Annotation-Instanz. Wenn man also eine Instanz einer konkreten Annotation simulieren will, kann man zu einer anonymen Unterklasse von AnnotationLiteral greifen, die man mit der entsprechenden Annotation parametrisiert:

Wem das zu kompliziert war, der merkt sich einfach, dass man auf diese Weise Annotations instanziieren kann.

Damit ist der erste Teil geschafft. Mit nur ein paar Zeilen Code habe ich eine Extension geschrieben, mit der Spring-Beans im CDI-Context verwendet (und auch automatisch injiziert) werden können. In meinem nächsten Blog werde ich die Extension dahingehend erweitern, dass auch in Spring die CDI-Beans verwendet und injiziert werden können.


Keine Kommentare

Kontakt

OPEN KNOWLEDGE GmbH

Standort Oldenburg:
Poststraße 1, 26122 Oldenburg

Standort Essen:
II. Hagen 7, 45127 Essen