Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 361
Patrick Auld Avatar asked Nov 06 '12 01:11

Patrick Auld


People also ask

How does Guice dependency injection work?

Guice figures out how to give you an Emailer based on the type. If it's a simple object, it'll instantiate it and pass it in. If it has dependencies, it will resolve those dependencies, pass them into it's constructor, then pass the resulting object into your object.

How do you inject a Guice class?

Client Application We need to create Injector object using Guice class createInjector() method where we pass our injector class implementation object. Then we use injector to initialize our consumer class. If we run above class, it will produce following output.

What is @named annotation in Guice?

Guice comes with a built-in binding annotation @Named that takes a string: public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... }

Can Guice inject null?

Guice forbids null by default So if something tries to supply null for an object, Guice will refuse to inject it and throw a NULL_INJECTED_INTO_NON_NULLABLE ProvisionException error instead. If null is permissible by your class, you can annotate the field or parameter with @Nullable .


1 Answers

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);
like image 134
Jeff Bowman Avatar answered Nov 02 '22 00:11

Jeff Bowman