Suche
Next Generation Application Development

Guru – Framework-Integration

Daniel Arndt und Marcel Birkner

Die ersten drei Artikel haben sich mit den Grundlagen und Features von CDI und der Testbarkeit von CDI-Applikationen beschäftigt. Dieser Artikel soll Ihnen die vielfältigen Erweiterungsmöglichkeiten vorstellen. Dabei besprechen wir das Service Provider Interface (SPI) inklusive der Portable Extensions und zeigen, wie die Erweiterung und Integration in CDI funktioniert. Zum Abschluss stellen wir zwei Projekte vor, die auf den Portable Extensions aufbauen, um existierende Frameworks mit CDI zu integrieren.

Der CDI Container ist nicht nur ein elegantes Programmiermodell, sondern auch offen für eigene Erweiterungen und Integrationen. So bietet CDI ein SPI (javax.enterprise.inject.spi) an, um Erweiterungen (Portable Extensions) für den CDI Container – unabhängig von dessen Implementierung – zu entwickeln oder mit Frameworks bzw. Bibliotheken von Drittanbietern zu integrieren. Insbesondere diese Aspekte waren in der Vergangenheit eine große Schwäche der Java EE und häufig eines des wichtigsten Argumente für die Verwendung anderer Frameworks wie z. B. Spring. Die Integration mit Frameworks oder Bibliotheken von Drittanbietern gestaltete sich äußerst schwierig, denn Enterprise Java Beans (EJB) verfügen bis dato nicht über eine SPI, um Erweiterungen für EJB-Komponenten oder Container Services zu entwickeln.

CDI eröffnet mit den Portable Extensions die Möglichkeit, ein ganzes Ökosystem an Erweiterung sowie Integrationen für die Java EE zu schaffen, und ist für viele die eigentliche Innovation. Hier ein Überblick über die Möglichkeiten, die eine Portable Extension mittels der SPI hat:

  • Erzeugen und Registrieren eigener Beans, Interceptors, Decorators, die nicht durch den CDI Container verwaltet werden
  • Erzeugen eigener Scopes
  • Injizieren von Abhängigkeiten in eigene Beans, Interceptors und Decorators
  • Hinzufügen oder Verändern von annotationsbasierten Metadaten

Die Möglichkeiten sind vielfältig: Zum Einstieg in das SPI bietet sich ein Beispiel an, das auf den PerformanceInterceptor aus dem vorherigen Artikel zurückgreift. Dort wurde die Bean MoneyTransferService mit dem InterceptorBinding @Performance annotiert und damit an den PerformanceInterceptor gebunden. Jetzt wird dieses Beispiel um einen Stereotyp @Service erweitert, womit jede Service Bean innerhalb der Beispielanwendung annotiert wird. Dieser Stereotyp kann nun dazu dienen, Cross-Cutting Concerns, wie z. B. den PerformanceInterceptor, an alle Service Beans mit genau diesem Stereotyp anzureichern, ohne diesen explizit über eine Annotation zu binden. Dieses Szenario kann über eine Portable Extension recht einfach gelöst werden.

@Stereotype
@Target({TYPE})
@Retention(RUNTIME)
public @interface Service {}


@Service
public class MoneyTransferServiceBean implements MoneyTransferService {
.
}

Der Ausgangspunkt zur Entwicklung einer Portable Extension ist die Implementierung einer Klasse mit dem Marker-Interface javax.enterprise.inject.spi.Extension, die als Java-SE-Service-Provider unter META-INF/services/javax.enterprise.inject.spi.Extension über ihren vollqualifizierenden Klassennamen registriert werden muss. Während des Initialisierungsprozesses des CDI Container wird eine ganze Reihe von Events ausgelöst, die durch eine Portable Extension mittels @Observe genutzt werden können. In diesem Beispiel wird das Event ProcessAnnotatedType verwendet. Dieses Event wird von dem CDI Container ausgelöst, bevor alle deklarierten Annotationen ausgewertet werden, und bietet sich somit sehr gut an, um bereits deklarierte Annotationen zu verändern, zu entfernen oder neue hinzuzufügen. Die Observer-Methode wird für jede Bean, die im CDI Container registriert wurde, aufgerufen. Wenn die Annotation @Service vorhanden ist, reichert die ServicePerformanceExtension an diesem Typ das InterceptorBinding @Performance mittels Dekorationen des AnnotatedType an (Listing 1). Der AnnotatedType ist eine Abstraktion der SPI für den Zugriff auf annotierte Metadaten an Klassen oder Interfaces. Der PerformanceInterceptor kann nun an jede mit @Service annotierte Bean gebunden werden, ohne explizit das InterceptorBinding zu deklarieren. Die ServicePerformanceExtension kann in ein eigenes JAR paketiert und abhängig von der jeweiligen Umgebung mit deployed werden, um transparent die Messung von Performancewerten an allen Service Beans zu ermöglichen.

Listing 1: Performance Extension
public class ServicePerformanceExtension implements Extension {

     void processAnnotatedType(@Observes ProcessAnnotatedType pat,
            BeanManager beanManager) {

        AnnotatedType target = pat.getAnnotatedType();

        if (target.isAnnotationPresent(Service.class)) {
        
            AnnotatedType decorated = new AnnotatedTypeDecorator(target) {

                @Override
                public Set getAnnotations() {
                    Set annotations = new HashSet();
                    annotations.addAll(super.getAnnotations());
                    annotations.add(new AnnotationLiteral() {
                    });
                    return annotations;
                }

            };

            pat.setAnnotatedType(decorated);

        }

Die zentrale Schnittstelle in der Entwicklung von Erweiterungen für CDI ist der javax.enterprise.inject.spi.BeanManager. Der BeanManager kann injiziert und zur Laufzeit genutzt werden.

@Inject BeanManager beanManager;

Der BeanManager ist eine Fassade zum CDI Container und bietet eine Vielzahl an Operationen für die direkte Interaktion mit dem Container an. So kann zum Beispiel programmatisch auf alle Beans, Interceptors, Decorators, Observers und Injection Points zugegriffen oder Events können versendet werden. Hier eine kleine Auswahl:

    // liefert alle Interceptors die mit @Performance annotiert sind
List> interceptors = beanManager.
.resolveInterceptors(InterceptionType.AROUND_INVOKE, new AnnotationLiteral() {});

// liefert alle Container registrierten Beans
Set> beans = beanManager.getBeans(Object.class, new AnnotationLiteral() {});

// prüft ob die Bean DebtLimitVerification ein Decorator ist
boolean isDecorator = beanManager
.createAnnotatedType(DebtLimitVerification.class)
.isAnnotationPresent(Decorator.class);

Weiteres zum BeanManager ist der SPI-Spezifikation zu entnehmen [1]. Jede Bean im CDI Container wird durch eine Instanz des Interfaces javax.enterprise.inject.spi.Bean repräsentiert. Über die Implementierung des Bean-Interfaces ermöglicht CDI eigene Beans, die z. B. durch ein anderes Framework verwaltet werden, im CDI Container verfügbar zu machen. Aus dem vorherigen Artikel ist der DebtLimitService bekannt, der in den CDI Decorator DebtLimitVerification injiziert wird.

@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
    
    @Inject DebtLimitService debtLimitService;

Dieses Beispiel wird dahingehend erweitert, dass die Implementierung des DebtLimitService durch das Spring Framework verwaltet wird. Dieser Service muss dem CDI Container als Bean verfügbar gemacht werden. Hierzu implementiert man eine Observer-Methode für das AfterBeanDiscovery-Event. Dieses Event wird durch den CDI Container ausgelöst, wenn die Ermittlung aller CDI Beans vollständig abgeschlossen wurde. Über dieses Event ist es möglich, eigene Beans, Interceptors, Decorators oder Observer-Methoden im CDI Container zu registrieren. Im folgenden Beispiel wird ein Spring ApplicationContext erzeugt, das Bean-Interface für den DebtLimitService implementiert und über die Methode addBean des AfterBeanDiscovery-Events im CDI Container registriert (Listing 2). Hervorzuheben an dieser Stelle sind die Methoden getQualifiers und create in der Bean-Implementierung. Die Methode getQualifiers liefert standardmäßig die Qualifiers Default und Any. Die Methode create liefert die Implementierung des DebtLimitSerivce über den Aufruf getBean des Spring ApplicationContext.

Listing 2: Observer-Methode für das AfterBeanDiscovery-Event
     void addDebtLimitService(@Observes AfterBeanDiscovery abd,
            BeanManager beanManager) {

        final ApplicationContext context = new ClassPathXmlApplicationContext(
                new String[] { "debtlimit.xml" });

        abd.addBean(new Bean() {

            public Class> getBeanClass() {
                return DebtLimitService.class;
            }

            public Set getQualifiers() {
                Set qualifiers = new HashSet();
                qualifiers.add(new AnnotationLiteral() {
                });
                qualifiers.add(new AnnotationLiteral() {
                });

            public Class extends Annotation> getScope() {
                return ApplicationScoped.class;
            }

            public Set getTypes() {
                Set types = new HashSet();
                types.add(DebtLimitService.class);
                types.add(Object.class);
                return types;
            }

            public DebtLimitService create(
                    CreationalContext creationalContext) {
                return (DebtLimitService) context.getBean(
                        getName(), DebtLimitService.class);
            }
            ...

        });

Geschrieben von
Daniel Arndt und Marcel Birkner
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.