Suche
Ein Helfer bei der sanften Migration?

CDI – lean für Alt und Neu

Sven Ruppert
©Shutterstock.com/maximmmmum

Wer kennt es nicht als Java-Entwickler aus dem Bereich der Desktop-Anwendungen. Die Kollegen aus dem JavaEE-Umfeld schwärmen von den Fähigkeiten der verwendeten Laufzeitumgebungen. Wie einfach es doch ist, entkoppelte und skalierbare Systeme zu bauen, Dank Dependency Injection (DI). Na, das gibt es doch auch auf der Desktop-Seite, CDI als JSR-299 sei Dank. Ansprechende GUIs mit HTML5, JSF und vielen anderen Frameworks auf der JavaEE-Seite. Für den Swing-Programmierer ein schweres Stück Arbeit, bis JavaFX2 auf den Markt gekommen ist. Aber leider ist auf der Desktop-Seite bisher keine offizielle Integration erfolgt. Wir nehmen uns dieser Problematik an und zeigen, dass es doch geht!

CDI ist allgemein dafür bekannt, dass damit lose gekoppelte Systeme gebaut werden können. Wann aber beginnt ein lose gekoppeltes System, wo endet es? Beginen wir mit einer einfachen und allgegenwärtigen Situation.

  public List<data> execute(){ ... };

Interfaces als Rückgabewert

An dem Beispiel aus Listing 1 sieht man, das wie in Java üblich eine Methode eine Liste vom Typ Data als Rückgabewert liefert. Durch die Verwendung des Interface ist der normale Sprachkonstrukt von Java verwendet worden um die Abhängigkeit zu der direkten Implementierung der Liste zu vermeiden. Soweit ist alles in Ordnung, wenn das Interface nicht gleichbedeutend mit einer einzigen Implementierung ist. Gehen wir also im weiteren Verlauf davon aus, dass es n Implementierungen zu diesem Interface geben wird. In diesem Beispiel natürlich eine nicht zu wiederlegende Behauptung, da selbst das JDK selbst eine Fülle an Implementierungen bereithält. Aber sehen wir uns doch die Implementierung der Methode selbst an.

Statische Semantik

Die Methode selbst ist in Listing 2 dargestellt und konzentriert sich hier ausschließlich auf die Instanziierung der Liste. Wie üblich wird hier mittels new eine Instanz der Klasse ArrayList erzeugt und der Variablen result übergeben die selbst vom Typ List ist. Die Variable result stellt ebenfalls den Rückgabewert der Methode dar.

  
import java.util.List;
import java.util.ArrayList;
public List<Data> execute(){
   final List<Data> result = new ArrayList<>();
   //....
   return result;
}

Ist das Konstrukt immer noch lose entkoppelt? Wenn man es genau nimmt, ist hier eine statische Abhängigkeit zu der Implementierung ArrayList vorhanden. Also alles andere als lose gekoppelt.

Auswirkungen statische Abhängigkeiten

Was also sind die Auswirkungen dieser vermeintlich nebensächlichen Abhängigkeit? Sicher meistens ist die Verwendung der Implementierung ArrayList eine sinnvolle Endscheidung. Aber denken wir einfach einmal an den Fall, wir müssten die Implementierung austauschen. In diesem Fall könnte sich das Refactoring mittels einfachster Methoden wie das Suche und Ersetzen der Import-Anweisung erfolgen. Ein neu kompilieren aller betroffenen Klassen incl einer Neuauslieferung muss nachträglich erfolgen. Das ist nicht in jedem Fall wünschenswert.

Version I: Minimierung der Auswirkungen

Einer der ersten Gedanken hierzu kann sein, eine Factory einzusetzen. Ein Beispiel ist in Listing 3 zu sehen. Hier werden verschiedene Ansätze gezeigt. Der erste ist, spezielle Methoden zur Erzeugung anzubieten. Das allerdings ist nur eine Verlagerung der Abhängigkeit von einem new Konstrukt auf einen Methodenaufruf. Die Endscheidung wird also zur Entwicklungszeit getroffen. Nachträgliches Ändern erfordert einen ähnlichen Aufwand wie zuvor. Zumindest gibt es keine abhängige Import-Anweisung mehr in der Ziel-Klasse. Das System ist schon ein wenig mehr entkoppelt.  Der zweite Ansatz der hier gezeigt wird ist die Verwendung einer neutralen Methode und der internen Fallunterscheidung mittels ContextResolver. Ein ContextResolver ist eine Einheit, die aus der derzeitigen Laufzeitumgebung die Endscheidung ableitet, welche Implementierung zu verwenden ist. Der spezifische ContextResolver ist selbst zu implementieren.

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.rapidpm.demo.cdi.commons.registry.ContextResolver;

public class ListFactory {
   public List createArrayList(){
      return new ArrayList();
   }
   public List createLinkedList(){
      return new LinkedList();
   }
   public List createList(){
      return new ArrayList();
   }

   public List createList(final ContextResolver contextResolver){
      //trivial Implementierung
      if(contextResolver == null){
         return createArrayList();
      } else{
      //triviale Fallunterscheidung
      if(contextResolver.resolveContext().equals(null)){
         return createArrayList();
      } else{
      return createLinkedList();
      }
   }
}


Die Verwendung aus Sicht eines Entwicklers sieht nun wie folgt aus. Die Abhängigkeit selbst, ist hier auf die Klasse ListFactory reduziert worden, jedoch ist die Klasse ContextResolver hinzugekommen. Wo also kommt die Instanz des ContextResolvers her? Verlagert man dieses in die Klasse ListFactory? Nehmen wir an, das die Verwendung der Klasse ContextResolver auf die Klasse ListFactory begrenz wird, dann ergibt sich ausschließlich eine Verbindung zur Klasse ListFactory. In Listing 3 würde also die Signatur der Methode createList() eine Reduktion um die Instanz der Klasse ContextResolver erfahren. 

import java.util.List;
public List<Data> execute(){
   final List list = new ListFactory().createList(contextResolver);
   //....
   return result;
}


Was allerdings hier nicht geschehen ist, ist die Entkopplung der Klasse ListFactory von allen Implementierungen des Interface List. Sollte es zur Laufzeit eine weitere Implementierung geben, so muss die Implementierung der Klasse ContextResolver sowie ListFactory modifiziert werden. 

Version II: Steigerung der Komplexität

Um nun die statischen Abhängigkeiten der ListFactory von den Implementierungen des Interface List zu realisieren ist einiges mehr an Aufwand notwendig. Die Implementierungen werden z.B. über Registries zur Verfügung gestellt auf die von der ListFactory zugegriffen werden kann. Die Entscheidung welche zu verwenden ist, wird durch den ContextResolver getroffen. Usw usw. Schnell steigt die Komplexität an, so dass man sich mit Recht fragen kann ob der Aufwand gerechtfertigt ist.

Version III: CDI

Die Verwendung von CDI beruht auf der Injektion von Instanzen. Listing  5 zeigt beispielhaft die Verwendung eines solchen Konstrukts incl. der Verwendung eines Producers.

import java.util.List;

@Inject @CDILegacyTest List list;

public List<Data> execute(){
   list.add(xyz); //Verwendung von List
   //....
   return result;
}

//in der Klasse ListProducer
@Produces @CDILegacyTest
public List createList(InjectionPoint injectionPoint, BeanManager 
      beanManager,ContextResolver contextResolver){
      //treffen der Entscheidungen...
      return new ArrayList();
}


CDI gibt einem die Möglichkeit, für jede Kombination aus Klasse/Interface und Qualifier eine eigene Klasse (den Producer) zu verwenden. Daraus folgt, dass eine Erweiterung durch das Hinzufügen weiterer Klassen erfolgen kann, ohne bestehende zu modifizieren.

Dreh- und Angelpunkt: der ContextResolver

Wie nun erfolgt die dynamische Rekonfiguration des Systems zur Laufzeit? Die Verwendung der Instance<T> ist der Einstiegspunkt. In unserem Beispiel würde das wie im Listing 6 aussehen.

import java.util.List;

@Inject @CDILegacyTest Instance<List> listInstance;

public List<Data> execute(){
// holen einer konkreten Instanz
   final List list = listInstance.get(); 
   list.add(xyz); //Verwendung von List
   //....
   return result;
}

//in der Klasse ListProducer
@Produces @CDILegacyTest
public List createList(InjectionPoint injectionPoint, BeanManager 
      beanManager,ContextResolver contextResolver){
      //treffen der Entscheidungen...
      return new ArrayList();
}

Damit kann die Entscheidung über die Erzeugung der verwendenden Instanz auf den Zeitpunkt der Verwendung verschoben werden. In diesem Moment sollten im System hinreichend Informationen zur Verfügung stehen, um die richtige Implementierung wählen zu können. Hier nun kommt der ContextResolver zum Einsatz. In diesem werden die Zustandsinformationen der Laufzeitumgebung, des Benutzers und der Applikationskontext aggregiert und führen zu einer deklarativen Entscheidung. Das Ergebnis ist ein umgebungsspezifisches AnnotationsLiteral mit dessen Hilfe die zur Verfügung stehenden Producer ausgewählt werden. Die Implementierung des ContextResolvers kann wiederum lean selektiert werden.

Fazit

CDI gibt einem die notwendigen Werkzeuge in die Hand einfach und mit geringer Komplexität, lose gekoppelte Systeme zu produzieren. Bei Einsatz dieser Designpattern bekommt der Entwickler einige weitere sehr interessante Features geschenkt. Der nächste Teil dieser Serie beschäftigt sich mit dem Aufbau von ContextRsolvern


Aufmacherbild: flat icons for web and mobile applications with beverages (flat design with long shadows) von Shutterstock / Urheberrecht: maximmmum

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare
  1. christian campo2013-10-23 11:56:06

    Warum nicht Guice ?

  2. Anton Epple2013-11-21 11:30:24

    Es sind offenbar einige Tippfehler in dem Artikel:

    "retrun result;"

    ...soll vermutlich sein:

    In Listing 2:
    -> return result;

    In Listing 4, 5 und 6:
    -> return list;

  3. Cloudia2013-11-22 13:48:30

    Danke, haben wir ausgebessert!

Schreibe einen Kommentar

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