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:
Binding in the configure() method. This method is not injected into and I can not get the base configuration.
Using a Provider/@Provides. Providers only bind a single instance.
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.
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.
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.
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.
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.
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) { ... }
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 .
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:
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.
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"));
}
}
You should never need to inject a Binder
(or anything else) into a Module.
Module
, the binder
will be a parameter of configure()
. If you extend AbstractModule
as you should, just call the binder()
method.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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With