Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guice assisted injection deeper down the dependency hierarchy

Tags:

java

guice

I want to conduct a chain of processing elements and wire them together via Guice. Let's assume the following path:

  • interface A implemented by class AImpl needs some input
  • interface B implemented by class BImpl needs A
  • interface C implemented by class CImpl needs B
  • interface D implemented by class DImpl needs C

The dependency of A can only be resolved at runtime and not at configuration time. The usual approach would be to use Assisted Injection in this case to create a factory, that takes the missing instances as parameters, just like this:

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

But what I actually want is something like this:

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

I don't want to manually pass AImpl-specific dependencies through the whole hierarchy. Is it possible to achieve this with Guice? If not, what's the best way to circumvent this problem elegantly while still retaining benefits of injection?

like image 558
orsg Avatar asked May 01 '13 10:05

orsg


People also ask

What is Guice dependency injection?

Guice is an open source, Java-based dependency injection framework. It is quiet lightweight and is actively developed/managed by Google. This tutorial covers most of the topics required for a basic understanding of Google Guice and to get a feel of how it works.

Which is the right way to inject in dependency?

Constructor injection should be the main way that you do dependency injection. It's simple: A class needs something and thus asks for it before it can even be constructed. By using the guard pattern, you can use the class with confidence, knowing that the field variable storing that dependency will be a valid instance.

What is dependency injection advantage?

A basic benefit of dependency injection is decreased coupling between classes and their dependencies. By removing a client's knowledge of how its dependencies are implemented, programs become more reusable, testable and maintainable.


1 Answers

Cheating way: Stick input in a static variable or singleton ThreadLocal. Set it before your pipeline starts and clear it after it ends. Bind everything else through DI.

Fancy way: In A, refer to a @PipelineInput String inputString but don't bind it in your main injector. Otherwise, bind dependencies as you normally would, including referring to @PipelineInput in other pipeline-related classes. When you do need a D, get it from your implementation of a DFactory, which I'm calling PipelineRunner.

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);
  }
}

Naturally, binding attempts for A, B, C, and D will fail outside of PipelineRunner for lack of a @PipelineInput String--you'll get a CreationException when you create the injector with those unsatisfied dependencies, as you discovered--but those pipeline-based dependencies should be easy to separate into a Module that you install into the child injector.

If this feels too hacky, remember that PrivateModules are also "implemented using parent injectors", and that the whole point of dependency injection is to make a dependency like inputForA available to the whole object graph in a decoupled way.

like image 97
Jeff Bowman Avatar answered Sep 22 '22 11:09

Jeff Bowman