Kolumne: EnterpriseTales

CDI geht fremd! Dependency Injection für JavaSE

Lars Röwekamp und Matthias Weßendorf

Dependency Injection ist beileibe nichts Neues [1]: Für fast jede Programmiersprache gibt es eine Vielzahl an Frameworks, die Hilfestellung bieten. Die Realisierung ist jedoch unterschiedlich: Ob nun mit oder ohne XML oder vollständig auf (Java-)Annotationen basiert – die Community hat viel zu bieten. Mit CDI (Contexts and Dependency) gibt es einen Standard für die Java-Welt, der sich besonders im Java-EE-Umfeld einfach nutzen lässt. Doch wie sieht es für die Freunde von Java SE aus? Müssen diese auf CDI verzichten?

Dem „klassischen“ Java Enterprise Developer dürfte CDI mittlerweile geläufig sein. Mit der Version 6 wurde die zugehörige Spezifikation – JSR 299 – in die JavaEE-Plattform aufgenommen. Der Standard definiert ein kontextabhängiges Komponentenmodell und stellt ein typensicheres Dependency-Injection-Framework auf Basis des JSR-330 [2] bereit. Zusätzlich bietet CDI ein mächtiges SPI, das die Entwicklung von portablen CDI-Erweiterungen ermöglicht.

Die vom (JavaEE-)Container verwalteten Komponenten können mithilfe von Annotationen Abhängigkeiten zu anderen Komponenten definieren. Ein JSF-basiertes Beispiel könnte wie in Listing 1 aussehen.

Listing 1

package et.cdi;

// JSR 330:
import javax.inject.Inject;
import javax.inject.Named;

// CDI/JSR 299:
import javax.enterprise.context.RequestScoped;

@Named("updateController")
@RequestScope
public class UpdateCustomerController {
...
    @Inject CustomerService customerService;
...
    public String saveChanges() {
        ...
        customerService.updateCustomer(customerObject);
        ...
    }
}

Sobald der JSF Controller innerhalb einer XHTML-Seite referenziert wird, erzeugt der JavaEE-Container eine Instanz und bedient automatisch alle Abhängigkeiten. Dieses spezifizierte Vorgehen stellt sicher, dass beim Aufruf der Action-Methode saveChanges() der benötigte Service auch wirklich zur Verfügung steht und aufgerufen werden kann.

Bootstrap – CDI in einer JavaSE-Umgebung

Soweit, so gut: Das Benutzen von CDI innerhalb einer „managed Environment“ ist denkbar einfach – da durch den Container automatisiert. Um eine CDI-Implementierung außerhalb eines JavaEE-Containers benutzen zu können, muss man dagegen selbst Hand anlegen. Nachfolgend wird am Beispiel von Apache OpenWebBeans gezeigt, wie man einen CDI-Container „von Hand“ startet (Listing 2).

Listing 2

package et.cdi;
...
// Apache OpenWebBeans:
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.spi.ContainerLifecycle;

public class MyApplication {

  // OWB Container Lifecycle
  private static ContainerLifecycle lifecycle = null;

  public static void main(String[] args) throws IOException {
    // get access to the lifecycle
    lifecycle = WebBeansContext.currentInstance().
          getService(ContainerLifecycle.class);

    // start it!
    lifecycle.startApplication(null);
    ...
    ...    
  }
}

Innerhalb der main-Methode wird der Container über ein Apache-OpenWebBeans-spezifisches API gestartet. Der Bootstrap-Code hat also eine direkte Abhängigkeit zu dem Apache-OpenWebBeans-Projekt und funktioniert nicht mit anderen CDI-Implementierungen, wie beispielsweise Weld [3] oder CanDI [4]. Das ist nicht unbedingt das, was man sich als Entwickler wünscht. Abhilfe schafft hier das in dieser Kolumne bereits vorgestellte Apache-DeltaSpike-Projekt [5]. DeltaSpike bietet ein neutrales API an, sodass containerspezifischer Code vermieden werden kann (Listing 3).

Listing 3

...
import org.apache.deltaspike.cdise.api.CdiContainer;
import org.apache.deltaspike.cdise.api.CdiContainerLoader;
...
   CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();
   cdiContainer.bootContainer();
...
   cdiContainer.shutdownContainer();
...

Derzeit unterstützt das Projekt den Apache-OpenWebBeans-Container. Support für Weld ist seit Kurzem ebenfalls verfügbar. Neben dem Starten und Stoppen des Containers bietet das DeltaSpike mit der BeanProvider-Klasse einen Hilfsmechanismus, um Komponenten durch den CDI-Container zu erzeugen. Als Beispiel wird ein einfacher Controller vorgestellt (Listing 4).

Listing 4

package et.cdi.se;

import javax.inject.Inject;
import javax.inject.Named;


@Named("updateController")
public class UpdateCustomerController {
    
    @Inject CustomerService service;
    
    public void saveChanges() {
        ...
        service.updateCustomer(customerObject);
    }        
}

Der Code ist fast identisch zum JSF-Controller aus dem JavaEE-Beispiel. Die @Named-Annotation wird genutzt, um die Komponente über ihren Namen zu laden. Durch die Verwendung der @Inject-Annotation am Service wird der CDI-Container angewiesen, die so deklarierte Abhängigkeit zu bedienen. Doch wie bekomme ich nun eine Instanz des Controllers, in der der CustomerService bereits gesetzt wurde? Wird der UpdateCustomerController mit einem gewöhnlichen Konstruktor (new) erzeugt, würde das nicht funktionieren. Der unterliegende CDI-Container muss angewiesen werden, das Objekt samt seiner Abhängigkeiten zu erzeugen (Listing 5).

Listing 5

...
import org.apache.deltaspike.core.api.provider.BeanProvider;
...
...
   UpdateCustomerController controller = 
     BeanProvider.getContextualReference(Controller.class, false);
   ...
   // save it:
   controller.saveChanges()
...

Sobald der CDI-Container läuft – also nach dem Aufruf von bootContainer() – kann eine Komponente durch den BeanProvider geladen werden. Im obigen Beispiel wurde dazu der Type des Controllers genutzt. Alternativ könnte auch der Wert der @Named-Annotation verwendet werden:

UpdateCustomerController controller = (Controller) BeanProvider.getContextualReference("updateController", false);

Dadurch, dass der Container angewiesen wurde, die UpdateCustomerController-Komponente zu erstellen, sind alle seine Abhängigkeiten bereits bedient, und der Aufruf von saveChanges() nutzt den gewünschten Service.

Fazit und Ausblick

Innerhalb von JavaEE werden CDI-basierte Komponenten, wie z. B. referenzierte JSF Controller, automatisch durch den JavaEE-Container instanziiert. Aber auch innerhalb von JavaSE lässt sich CDI nutzen: Mit ein paar Zeilen Code kann der CDI-Container für die Verwaltung der Komponenten verwendet werden. Dieser Artikel zeigt das Starten und Stoppen, sowie das „Lookup“ von Komponenten. In einer zukünftigen Ausgabe der Kolumne werden wir zeigen, wie auch weitergreifende Konzepte, z. B. CDI-Events, innerhalb einer JavaSE-Umgebung genutzt werden können.

Lars Röwekamp ist Geschäftsführer der open knowledge GmbH und berät seit mehr als zehn Jahren Kunden in internationalen Projekten rund um das Thema Enterprise Computing (Twitter: @mobileLarson).

Matthias Weßendorf arbeitet für die Firma Kaazing. Dort beschäftigt er sich mit WebSocket, HTML5 und weiteren Themen rund um das Next Generation Web. Er bloggt regelmäßig auf http://matthiaswessendorf.wordpress.com (Twitter: @mwessendorf).

Geschrieben von
Lars Röwekamp und Matthias Weßendorf
Kommentare

Schreibe einen Kommentar

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