Guice: is it possible to inject modules?

后端 未结 4 1239
Happy的楠姐
Happy的楠姐 2020-12-13 14:13

I have a Module that requires some Depedency. Is there a way Modules themselves can be injected? I realize this is a bit of a chicken and egg situation...

相关标签:
4条回答
  • 2020-12-13 14:33

    Using a provider or @Provides methods are great if you need a dependency to manually construct an object. However, what if you need something to help you decide how to configure the bindings themselves? It turns out you can use Guice to create (and configure) your module.

    Here is a (contrived) example. First, the module we want to configure:

    /**
     * Creates a binding for a Set<String> which represents the food in a pantry.
     */
    public class PantryModule extends AbstractModule {
      private final boolean addCheese;
    
      @Inject
      public ConditionalModule(@Named("addCheese") boolean addCheese) {
        this.addCheese = addCheese;
      }
    
      @Override
      protected void configure() {
        Multibinder<String> pantryBinder = Multibinder
          .newSetBinder(binder(), String.class);
    
        pantryBinder.addBinding().toInstance("milk");
    
        if (addCheese) {
          pantryBinder.addBinding().toInstance("cheese");
        }
    
        pantryBinder.addBinding().toInstance("bread");
      }
    }
    

    The PantryModule expects a boolean value to be injected to decide whether or not it should include cheese in the pantry.

    Next, we'll use Guice to configure the module:

    // Here we use an anonymous class as the "configuring" module. In real life, you would 
    // probably use a standalone module.
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        // No cheese please!
        bindConstant().annotatedWith(Names.named("addCheese")).to(false);
        bind(PantryModule.class);
      }
    });
    
    Module configuredConditionalModule = injector.getInstance(PantryModule.class);
    

    Now that we have a configured module, we'll update our injector to use it...

    //...continued from last snippet...
    injector = injector.createChildInjector(configuredConditionalModule);
    

    And finally we'll get the set of strings that represent our pantry:

    //...continued from last snippet...
    Set<String> pantry = injector.getInstance(new Key<Set<String>>() {});
    
    for (String food : pantry) {
      System.out.println(food);
    }
    

    If you put all the pieces together in a main method and run it, you'll get the following output:

    milk
    bread
    

    If you change the binding to the "addCheese" boolean to true, you'll get:

    milk
    cheese
    bread
    

    This technique is cool, but probably only useful when you have control over the Injector instance and only when the module requires complex dependencies. Nonethless, I found a real need for this on a real project at work. If I did, then someone else might too.

    0 讨论(0)
  • The question is already well answered, but I just wanted to add a variation to Colin's example:

    class MyModule extends AbstractModule { 
      public void configure() {
        bind(Something.class).toProvider(new Provider<Something>() {
           @Inject Dependency d;
           Something get() { return d.buildSomething(); }
        }
      }
    }
    

    The @Provides method approach is clearer than what I have above for this simple case, but I've found that instantiating an actual Provider can be useful in some situations too. Something I stole from the mailing list; wouldn't have occurred to me on my own ;)

    0 讨论(0)
  • 2020-12-13 14:38

    @Provides methods can take dependencies as parameters just like parameters to an @Inject annotated constructor or method:

    @Provides Something provideSomething(Dependency d) {
       return new Something(d); // or whatever
    }
    

    This is documented here, though perhaps it could be made to stand out more.

    0 讨论(0)
  • 2020-12-13 14:58

    What is the problem with initializing the module just by calling new MyModule(d) or by creating a Provider<Something> that has an injected Injector? Those would appear to be the standard ways of handling this sort of problem. As has been mentioned, you can also use @Provides methods with arguments.

    If the dependency is optional then you can create the module and then call a setter to initialize the value if needed (e.g., com.google.inject.persist.jpa.JpaPersistModule does this with properties, while using new JpaPersistModule(String) to load the correct configuration).

    Otherwise I suppose it might be possible to do so (and then call createChildInjector(Modules... modules)), but I'd almost always prefer one of the other approaches to that one.

    0 讨论(0)
提交回复
热议问题