How do you make dynamic bindings in Guice that require an injected Instance?

10,783

Remember that all of the configure methods configure all of the bindings in an Injector before any injection can happen. That said, a few things:

  1. Binding @Named properties to the contents of a single Properties instance is so useful, there's a Names.bindProperties(...) method that does it automatically for you. The only trick is that you need to have the Properties instance at the time configure() is run.

    If they're all available at the same time, don't worry about binding the properties in one module and binding the application in another. As long as they all go into the same Injector, Guice will combine them all and let them satisfy each others' dependencies.

  2. Providers can return different instances, and usually do--but you're right that it won't help you differentiate between keys. If injecting the Properties instance directly is too ugly, consider making a lightweight factory instead:

    public class ConfigOracle {
      @Inject private Properties properties;
    
      public String getAsString(String key) { ... }
      public int getAsInt(String key) { ... }
    }
    
    public class SomeConfigUser {
      @Inject private ConfigOracle configOracle;
    
      public void doStuff() {
        doStuffBasedOn(configOracle.getAsString("my.properties.key"));
      }
    }
    
  3. You should never need to inject a Binder (or anything else) into a Module.

    • If you implement Module, the binder will be a parameter of configure(). If you extend AbstractModule as you should, just call the binder() method.
    • You can pass in dependencies through constructor arguments to the Module, if need be, which (as far as I'm concerned) is the only way Modules should vary the bindings they create.
    • There's no reason you couldn't create a Module through an Injector, but you'd have to have an Injector first, and it sounds like you're trying to get away with only having one.
    • If you need other instances from the Injector you can always write a Provider implementation with @Inject fields/methods/constructors, or even take in parameters in a @Provides method (which will be filled in with dependencies automatically).

Overall I still favor the child injector approach (thanks for the link and compliment to my previous answer!), which fits your "dynamic bindings based on an injected instance" description the best, and would literally be this simple:

class PropertiesModule extends AbstractModule {
  Properties properties;

  PropertiesModule(Properties properties) {
    this.properties = properties;
  }

  @Override public void configure() {
    Names.bindProperties(binder(), properties);
  }
}

Injector oldInjector = Guice.createInjector(allYourOtherModules);
Module myModule = new PropertiesModule(oldInjector.get(Properties.class));
Injector injector = oldInjector.createChildInjector(myModule);
Share:
10,783
Patrick Auld
Author by

Patrick Auld

"It is better to ask forgiveness than permission"

Updated on June 04, 2022

Comments

  • Patrick Auld
    Patrick Auld almost 2 years

    I'd like to create a Module that dynamically binds instances to named annotations. The use case is I would like to automatically bind the values in my configuration with the key in the properties file being the @Named value.

    However the configuration is bound in a different module so I need the config to be injected. Solutions I've looked at are:

    1. Binding in the configure() method. This method is not injected into and I can not get the base configuration.

    2. Using a Provider/@Provides. Providers only bind a single instance.

    3. Using MultiBinder. My use case is a little different then what is provided by this extension. Multi-binding allows you to bind multiple instances separately and then have them injected as a Collection more complex containing type. I would like to bind each instance separately and have them by uniquely identifiable for injection latter.

    4. Use a childInjector. Unfortunately this isn't possible without some extensive modification of existing code. This answer is a very good description of how to solve this problem this way though.

    5. Inject the binder somehow. (I started getting a little hackier) Guice allows injecting the Injector for latter use, I tried injecting the Binder into the Module though a @Provides method and then using the binder directly to make multiple binds within the method. Guice would not inject the binder.

  • Patrick Auld
    Patrick Auld over 11 years
    Another good answer, thanks! I didn't know about the Names.bindProperties() method, I'll have to save that for latter. I tried the solution you propose in 2 last night and it mostly worked, although wasn't as clean I would have have liked it. I basically came to the conclusion that what I'd really like to do falls outside of Guice's design. I don't own the framework I'm deploying in and I don't believe that child injectors will be an available feature anytime soon so I'm going to rework our configuration a little differently. Thank you for the prompt answer though!