In der vergangenen Woche habe ich in unseren Seminarräumen in Berlin ein Training Java EE Masterclass durchgeführt, worin neben JPA, EJB und JSF auch das Thema CDI behandelt wird. Mit den engagierten Teilnehmern entwickelte sich insbesondere am Kursende eine interessante Diskussion zu diversen weiterführenden Themen, u. a. darüber, ob es möglich sei, eine Bibliothek so aus CDI-Komponenten aufzubauen, dass diese innerhalb der Bibliothek beliebig zur Injektion bereit gestellt werden, eine die Bibliothek nutzende Anwendung jedoch nur bestimmte Beans daraus angeboten bekommt, also bspw. so:
- Library
cdi-injectfilter-lib
- definiert einen Service
GreetingService
, der mit Hilfe eines weiteres ServicesPartOfDayService
realisiert wird. GreetingService
undPartOfDayService
sind CDI Beans, die mittels Injektion miteinander verknüpft werden.
- definiert einen Service
- Anwendung
cdi-injectfilter-web
- nutzt
GreetingService
auscdi-injectfilter-lib
mittels Injektion, - soll
PartOfDayService
nicht (mittels Injektion) nutzen.
- nutzt
Die spontane Idee, PartOfDayService
durch Annotation mit @Vetoed
(bzw. @Typed
) aus dem (CDI-) Rennen zu nehmen, greift zu kurz, weil dann nicht nur die Injektion eines PartOfDayService
in cdi-injectfilter-web
verhindert wird, sondern die Nutzung von PartOfDayService
als CDI Bean generell, also auch in cdi-injectfilter-lib,
unterdrückt wird.
Gesucht ist also eine Möglichkeit, die Injektion einer CDI Bean in Abhängikeit vom Injektionsziel zu erlauben oder verwehren. Dies gelingt relativ einfach damit, dass die betreffende Bibliothek – im Beispiel cdi-injectfilter-lib
– zu einer CDI Extension gemacht wird. Dann können mit Hilfe eines Observers für den Lifecycle Event, den der Container während des Anwendungsdeployments für eine Injektionsstelle feuert, unerwünschte Injektionen abgelehtn werden.
Die folgenden Schritte sind dafür nötig:
- Bibliothek zu einer CDI Extension machen:
- Klasse als Implementierung von
javax.enterprise.inject.spi.Extension
entwerfen und im Service DescriptorMETA-INF/services/javax.enterprise.inject.spi.Extension
registrieren
(⇒de.gedoplan.beantrial.cdi.injectfilter.lib.extension.InjectFilterExtension
), - darin eine Observer-Methode für den Event
ProcessInjectionPoint<T, X>
vorsehen. Diese Methode wird vom Container während des initialen Scans für jeden erkannten Injektionspunkt aufgerufen. Am übergebenen Event-Objekt kann man erkennen, was wohin injiziert werden soll. Unerwünschte Kombinationen können durch Aufruf der Event-MethodeaddDefinitionError
mit Schimpf und Schande belegt werden.
- Klasse als Implementierung von
- Zur Unterscheidung der nur innerhalb des Bibliotheksmoduls frei nutzbaren Beans von denen, die auch ausserhalb injizierbar sein sollen, empfiehlt sich die Einführung eines entsprechenden CDI Qualifiers
(⇒de.gedoplan.beantrial.cdi.injectfilter.lib.LibraryService
).
Die Injektion innerhalb der Library geschieht dann (natürlich) ausschliesslich unter Nutzung dieses Qualifiers.
In der o. a. Observer-Methode können die betroffenen Injektionsstellen anhand des Qualifiers erkannt werden und Injektionen in Ziele ausserhalb der Library abgelehnt werden. - Eine unerwünschte Injektion sollte nun mit einen Deployment-Fehler belegt sein
(⇒de.gedoplan.beantrial.cdi.injectfilter.web.presentation.ServicePresenter
).
Der gesamte Beispielcode mit den genannten Klassen (⇒ …) findet sich auf GitHub.
Es sei darauf hingewiesen, dass die geschilderte Lösung ein paar kleine Contras hat:
- Der Beispielcode macht die “Injektionserlaubnis” am Paket der Zielklasse fest. Das setzt eine “gutmütige” Paketstruktur voraus. Würde ein(e) “kreative(r)” Entwickler(in) der nutzenden Anwendung die Beans in Paketen platzieren, deren Name mit denen der genutzten Bibliothek übereinstimmen, würde er/sie die Injektionseinschränkung damit umgehen.
- Ebenfalls umgehen ließe sich die Injektionseinschränkung durch die Bereitstellung eines passenden Producers in der aufrufenden Anwendung.
- Die Einschränkung wird erst zum Deployent-Zeitpunkt erkennbar. Eine IDE wie Eclipse oder NetBeans wird vorher nicht warnen, wenn eine unerwünschte Injektion programmiert wird.
Die ersten beiden Nachteile treten bei “gutmütiger” Softwareentwicklung nicht auf. Oder anders gesagt: Denen mit zuviel kreativer Energie kann so nicht geholfen werden. Der dritte Nachteil wird durch die empfohlene Nutzung eines Qualifiers zumindest weitgehend entschärft – soweit der Quallifier nicht kreativ misbraucht wird – aber das hatten wir ja schon.
Viel Spass/Erfolg mit komponentenbasierter Softwareentwicklung auf Basis von Java EE!
Zur Vertiefung sei auf unser umfangreiches Seminarangebot hingewiesen: GEDOPLAN IT Training. Alle Kurse gibt es auch kundenspezifisch angepasst!