Die Kraft des Saftes: Dependency Injection


Theoretisch waren mir die Vorteile von Dependency Injection schon länger klar, aber der Einsatz in der Praxis ist eben doch etwas anderes, so ungefähr wie wenn man das erste Mal einen Fahrradhelm aufzieht: Es ist ungewohnt und etwas unbequem, aber man hat das Gefühl, dass man das Richtige tut…

Und so habe ich denn begonnen, bei einem kleineren, lokalen Java-Projekt ohne harte Deadline von Anfang an Google Guice zu verwenden. Von den vorhandenen Alternativen schien es mir die leichtgewichtigste zu sein, und auch seine auf Annotations basierende Architektur sagte mir zu. Trotzdem musste ich gewaltig umdenken und auch ein paar (schlechte?) Gewohnheiten über Bord werfen.

Die Konfiguration der Klassen, die DI benötigen, erfolgt bei Guice über sogenannte Module. Diese Module werden dem sogenannten Injector übergeben, der dann das Injizieren übernimmt. Normalerweise geschieht das nur einmal in einer zentralen Klasse: Der Injektor konstruiert einem die benötigten Objekte mit den in den Modulen spezifizierten (oder „gebundenen“) Konstruktor-Parametern. Der Witz ist, dass diese Konstruktor-Parameter selber wieder Parameter injiziert bekommen können und so weiter und so fort. Konstruktor-Injektion ist also „ansteckend“, und erst dadurch skaliert das ganze Konzept – es wäre furchtbar unpraktisch und würde der Idee von DI widersprechen, wenn man stattdessen überall den Injector herumreichen müsste. Guice unterstützt auch andere Formen der Injektion (etwa „nachträgliche“ Injektion bei Objekten, die aus Factories stammen), aber darauf will ich hier nicht eingehen.

Eine gute Idee ist, jedem Package sein eigenes Modul zu spendieren, denn dann kann man die Implementierungen für ein Interface package-private machen (so sie denn im gleichen Package stehen). Zuerst war ich skeptisch, weil man ja dem Injector alle Module „hartkodiert“ übergeben muss, aber es fand sich (hier) eine elegante Lösung: Der ServiceLoader von Javas eingebauten SPI-Mechanismus. Das sieht dann etwa so aus:

public static void main(String... args) {
    final Injector injector = Guice.createInjector(
        ServiceLoader.load(com.google.inject.Module.class));
    injector.getInstance(MeineHauptklasse.class);
}

Die einzelnen Module listet man dann einfach in einer Text-Datei META_INF/services/com.google.inject.Module auf (eine ausführliche Beschreibung von SPI findet sich hier).

Die Idee, ServiceLoader mit Guice zu koppeln, lässt sich auch anderswo verwenden. In meiner Applikation habe ich mehrere, voneinander weitgehend unabhängige Tabs, und es wäre natürlich schön, diese dynamisch einbinden zu können, ähnlich wie Plugins. Auch das funktioniert wie geschmiert. Zuerst benötigen wir ein gemeinsames Interface und Implementierungen:

public interface TabProvider {
   public JPanel getPanel();
}
 
public class FooTabProvider implements TabProvider {
   public JPanel getPanel(){...};
}
 
public class BarTabProvider implements TabProvider {
   public JPanel getPanel(){...};
}

Nun verknoten wir das in einem Guice-Modul mit einen ServiceLoader. Die magische Zutat, mit der man gleich ein Set von Werten injizieren kann, heißt „Multibinder“:

public GuiModule implements com.google.inject.AbstractModule {
    @Override
    protected void configure() {
        Multibinder<TabProvider> tabBinder = Multibinder.newSetBinder(binder(), TabProvider.class);
        for(TabProvider tabProvider : ServiceLoader.load(TabProvider.class)) {
            tabBinder.addBinding().toInstance(tabProvider);
        }
    }
}

Natürlich müssen jetzt die einzelnen Module wie vorhin in einer Text-Datei mit dem Namen des Interfaces, also z.B. META-INF/services/my.package.TabProvider, aufgelistet werden. Nun können alle TabProvider an der richtigen Stelle als Set injiziert werden:

 
public class MyMainFrame {
  @Inject
  public MyMainFrame(Set<TabProvider> tabProviders) {
     ...
  }
  ...
}

Ein Set garantiert natürlich keine Reihenfolge, aber ich habe mich beholfen, indem ich die Implementierungen einfach die Tab-Position als Zahl zurückliefern lasse. Wenn man nach alter BASIC-Manier die Positionen in Zehnerschritten wählt, kann man später immer noch andere Tabs dazwischen einfügen.

Eine andere nützliche Methode, um die Bindungen in den Modulen über eine Datei konfigurieren zu können, ist die Verwendung der guten alten Properties-Dateien:

public MyModule extends AbstractModule {
    @Override
    protected void configure() {
       try {
          Properties properties = new Properties();
          properties.load(new FileReader("my.properties"));
          Names.bindProperties(binder(), properties) 
       } catch(Exception ex) {
           ...  
       } 
   } 
}

Danach kann man die Properties wie folgt injizieren lassen:

public class Client {
   @Inject
   public Client(@Named("url") String url) {
      ...
   }
} 

Das soll es erst einmal für heute mit Guice und Java gewesen sein. Es drängt sich natürlich die Frage auf: Und was ist mit Scala? Ich kann mir vorstellen, dass es Situation gibt, in denen Guice auch in Scala vorteilhaft wäre, aber in den meisten Fällen sind Scalas „Bordmittel“ meiner Meinung nach ausreichend. Glücklicherweise brauche ich darüber nicht viel zu schrieben, denn dieser exzellente Artikel von Jonas Bonér listet verschiedene Möglichkeiten auf und diskutiert ihre Vor- und Nachteile. Ein Gebiet, auf dem meiner Meinung nach Guice glänzen könnte, wäre die Kopplung von Scala und Java: Ein Guice-Modul ist ein guter Platz, um Häßlichkeiten (man denke an $MODULE oder varargs) beim Aufruf der jeweils fremden Sprache zu verstecken.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s