16. November 2010 | 3 min lesezeit

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

In meinem letzten Blog-Eintrag habe ich gezeigt, wie man eine CDI-Extension schreibt, mit der man Spring-Beans in CDI-Beans injizieren kann. Ich möchte diese Extension in diesem Blog-Eintrag nun dahingehend erweitern, dass auch die CDI-Beans in Spring-Beans injiziert werden können.

Spring verwendet für Bean-Medadaten das Interface org.springframework.beans.factory.config.BeanDefinition. Für jede CDI-Bean müssen wir eine Instanz davon in Spring registrieren und dann das Erzeugen und Zerstören der Beans für diese Bean-Definitionen von Spring übernehmen. Für das Erzeugen und Zerstören von Beans ist in Spring der Scope zuständig. Wir werden also einen eigenen Scope für die CDI-Beans definieren. Hierzu implementieren wir das Interface org.springframework.beans.factory.config.Scope. Interessant sind hier die beiden Methoden get(…) und remove(…).

Die erste Methode muss zu einem Bean-Namen die aktuelle Instanz aus dem Scope liefern und die zweite muss die aktuelle Instanz aus dem Scope entfernen, wenn es eine Instanz gibt. Diese beiden Operationen müssen direkt auf dem CDI-Container durchgeführt werden. Das Interface zum CDI-Container ist der BeanManager und um von diesem aktuelle Instanzen zu erhalten, muss man die jeweilige Bean-Implementierung der Instanz parat haben. Unser Scope benötigt also den BeanManager und eine Map, die uns zu einem Spring-Bean-Namen die CDI-Bean liefert:

Das Interface „Scope“ hat noch die Methoden registerDestructionCallback, getConversationId und seit Spring 3 auch noch resolveContextualObject. RegisterDestructionCallback ist für uns irrelevant, da die Destruction der CDI-Beans vom CDI-Container übernommen wird. Dieser Call kann also ignoriert werden. GetConversationId und resolveContextualObject sind optionale Operationen. Hier können wir getrost immer null zurückliefern.

Putting it all together

Mit diesem Scope können wir also dem Spring-Container CDI-Beans unterschieben. Ein paar Fragen bleiben allerdings noch offen: Woher kriegen wir die CDI-Beans für den Scope-Konstruktor und wie initialisieren wir den Spring-Scope? Die Bean-Implementierungen aller CDI-Beans eines CDI-Containers zu erhalten ist tatsächlich nicht so einfach. Wer erwartet hätte, dass man wie bei Spring einfach den BeanManager nach getBeans() oder ähnlichem fragen kann, wird leider enttäuscht. Hier helfen uns aber wieder die Lifecycle-Events des Containers weiter. Wir haben bereits im vorherigen Blog-Eintrag gesehen, dass wir das AfterBeanDiscovery-Event empfangen können. Genauso gibt es im CDI-Container das ProcessBean-Event, dass vom Container immer dann erzeugt wird, wenn er eine neue Bean gefunden hat. Wir erweitern also die Extension aus dem vorherigen Blog-Eintrag um ein Attribut cdiBeans, in dem wir die CDI-Beans sammeln und um eine Methode, um das ProcessBean-Event zu empfangen:

Da die BeanFactory in Spring nach Erstellung des ApplicationContexts geschlossen ist, können wir dann weder Bean-Definitionen noch einen eigenen Scope registrieren. Die Registrierungen müssen also während der Initialisierung der BeanFactory stattfinden. Hierzu definieren wir einen BeanFactoryPostProcessor. Neben der Registrierung des Scopes muss dieser auch Namen für die Bean-Definitionen erzeugen, weil jede Spring-Bean (im Gegensatz zu CDI-Beans) einen eindeutigen Namen benötigt.

Das Erzeugen der Bean-Definitionen ist in Spring recht einfach, da Spring eine generische Implementierung für das BeanDefinition-Interface mitliefert, das wir verwenden können. Die Implementierung der Methode wird dadurch recht einfach:

Durch das Setzen unseres CDI-Scopes wird sichergestellt, dass Spring immer unsere Scope-Implementierung fragt, wenn eine Instanz benötigt wird, wodurch wir die Brücke zu CDI herstellen. Die Methode createBeanName(…) ist auch recht einfach implementiert, wenn man einmal weiß, wie Spring seine Bean-Namen generiert. Schlüssel ist hier die BeanDefinitionRegistry, mit der doppelte Bean-Namen vermieden werden können:

Jetzt müssen wir nur noch unseren Post-Processor an den Spring-Context hängen und nach einem Aufruf von context.refresh() haben wir unsere CDI-Beans im Spring-Container. Am sinnvollsten tun wir das in der Methode connectCdiAndSpring. Eine kleine Anpassung ist dort noch notwendig: Wir schieben ja in dem Post-Processor alle CDI-Beans Spring unter, um danach alle Spring-Beans CDI unterzuschieben. Dies führt allerdings dazu, dass nach dieser Aktion jede CDI-Bean doppelt vorhanden ist (einmal direkt und einmal über den Umweg als Spring-Bean). Wir müssen hier also alle Spring-Beans auslassen, die als CDI-Beans Spring-Beans geworden sind, also alle die in unserem CDI-Scope liegen:

Kleine Randnotiz: Wir benutzen hier den Konstruktor des ApplicationContexts, der nicht automatisch ein refresh() auf der Bean-Factory ausführt (Parameter „false“), weil wir das refresh() später manuell ausführen und so ein Doppel-Refresh() vermeiden.

Damit ist unsere Spring-Integration in CDI (und damit in JavaEE fertig). Das Ganze sind nur etwas mehr als 200 Zeilen Code und beinhaltet einiges Wissen über Interna der beiden Technologien. Ich hoffe ich konnte neben der Umsetzung der Extension auch einen kleinen Einblick in diese Interna bieten.


Keine Kommentare

Kontakt

OPEN KNOWLEDGE GmbH

Standort Oldenburg:
Poststraße 1, 26122 Oldenburg

Standort Essen:
II. Hagen 7, 45127 Essen