2013-05-01 13 views
11

Ich möchte eine Kette von Verarbeitungselementen leiten und sie über Guice miteinander verbinden. Lassen Sie uns den folgenden Pfad übernehmen:Guice assistierte Injektion tiefer in die Abhängigkeitshierarchie

  • interface A implementiert durch class AImpl braucht eine Eingabe
  • interface B implementiert durch class BImpl Bedürfnisse A
  • interface C implementiert durch class CImpl Bedürfnisse B
  • interface D implementiert durch class DImpl Bedürfnisse C

Die Abhängigkeit von A kann nur zur Laufzeit und nicht zur Konfigurationszeit gelöst werden. Die übliche Vorgehensweise wäre Assisted Injection in diesem Fall zu verwenden, um eine Fabrik zu erstellen, die die fehlenden Instanzen als Parameter nehmen, so wie diese:

public interface AFactory { 
    public A createA(String input); 
} 

Aber was ich will eigentlich so etwas wie diese:

public interface DFactory { 
    public D createD(String inputForA); 
} 

Ich möchte AImpl-spezifische Abhängigkeiten nicht durch die gesamte Hierarchie manuell übergeben. Ist es möglich, dies mit Guice zu erreichen? Wenn nicht, was ist der beste Weg, um dieses Problem elegant zu umgehen, während die Vorteile der Injektion erhalten bleiben?

Antwort

7

Cheating Weg: Stick input in einer statischen Variable oder Singleton ThreadLocal. Stellen Sie es vor dem Start der Pipeline ein und löschen Sie es, nachdem es beendet wurde. Bind alles andere durch DI.

Fancy Art und Weise: In A, beziehen sich auf ein @PipelineInput String inputString aber bindet es nicht in Ihrem Haupt-Injektor. Andernfalls binden Sie Abhängigkeiten wie gewohnt, einschließlich @PipelineInput in anderen pipelinebezogenen Klassen. Wenn Sie eine D benötigen, erhalten Sie es von Ihrer Implementierung einer DFactory, die ich PipelineRunner aufrufen.

public class PipelineRunner { 
    @Inject Injector injector; // rarely a good idea, but necessary here 

    public D createD(final String inputForA) { 
    Module module = new AbstractModule() { 
     @Override public void configure() { 
     bindConstant(inputForA).annotatedWith(PipelineInput.class); 
     } 
    }; 
    return injector.createChildInjector(new PipelineModule(), module) 
     .getInstance(D.class); 
    } 
} 

Natürlich Bindungsversuche für A, B, C und D außerhalb von PipelineRunner mangels eines @PipelineInput String scheitern --you'll eine CreationException zu erhalten, wenn Sie den Injektor mit diesen unbefriedigten Abhängigkeiten schaffen, wie Sie entdeckt - aber diese pipeline-basierten Abhängigkeiten sollten einfach in ein Modul zu trennen sein, das Sie in den Kind-Injektor installieren. Wenn sich dies zu hackig anfühlt, denken Sie daran, dass PrivateModule auch "implemented using parent injectors" sind, und dass der ganze Punkt der Abhängigkeitsinjektion eine Abhängigkeit wie inputForA für das gesamte Objektdiagramm auf entkoppelte Weise verfügbar macht.

+0

Ich muss mich an meine Zustimmung erinnern, weil: "Injektionsversuche für A, B , C und D werden außerhalb von PipelineRunner fehlschlagen, wenn keine @PipelineInput-Zeichenfolge vorhanden ist "Dies funktioniert nicht, da Guice Injectors zur Konfigurationszeit überprüft und fehlschlägt, sobald eine nicht erfüllte Bindung erkannt wird, weshalb Sie fast keine erstellen können kompletter Injektor, der von seinem Kind vervollständigt wird. Private Module überwinden dies mit einem speziellen Bindemittel. – orsg

+0

Mein Fehler; Sie müssen A, B, C und D an die Konstante binden. Trivial zu beheben, obwohl. Antwort aktualisiert –

+1

Okay, also muss ich alles, was mit der Pipeline zusammenhängt, ausschließlich innerhalb des Kind-Injektors binden und der Eltern-Injektor kennt nur die Fabrik ("PipelineRunner"). – orsg

1

Ich sehe drei Optionen. Sie hängen davon ab, wie oft Sie die input für A ändern.

1) Binden Sie input als eine Konstante in Ihrem Modul. Dies funktioniert nur, wenn Sie diesen Wert kennen, bevor Sie den Injector erstellen und den Wert nie ändern möchten. Siehe bindConstant

2) Verwenden Sie ein privates Submodul, das entweder A oder den Wert für input innerhalb dieses Moduls bindet. Grundsätzlich können Sie zwei oder drei Instanzgraphen mit unterschiedlichen Werten haben. Siehe newPrivateBinder.

3) Verwenden Sie eine Scope ala RequestScope, SessionScope ... Auf diese Weise kann die Eingabe häufig ändern können, aber Sie muss den Umfang an einem gewissen Punkt betreten/verlassen definiert werden. Ein Beispiel finden Sie in Custom Scopes.

+0

1) ist zu spät und 2) ist nur ein Sonderfall von 1). Ich habe auch über Scopes nachgedacht, aber das ist so viel Standard für solch ein kleines Problem, dass ich DI lieber aus Gründen der Eleganz auslassen würde: -/ – orsg