If you know that @AutoConfig(provider = JsonConfigProvider) ConfigLoader<?> jsonConfig will return the exact results jsonConfigProvider.get() , and JsonConfigProvider obviously has an open constructor without parameters for newInstance to work, why don't you just ask JsonConfigProvider first of all?
Basically, Guice is just a Map<Key, Provider> with fancy packaging. The bad news is that it makes variable bindings like "bind Foo<T> for all T" impossible to express briefly, and that includes your "bind @Annotation(T) Foo for all T". The good news is that you still have two options.
Bind each vendor separately
Although you cannot check annotations at the time of submission (or tell Guice to do this for you), Guice will compare annotations using their equals methods if you are linking an instance of the annotation and not an annotation class (like you would with Names.named("some-name") ). This means that you can bind ConfigLoader<?> To each expected annotation in the module. Of course, this also means that you will need to have a list of possible ConfigLoader providers available at setup time, but they must be compile-time constants if you use them as annotation parameters.
This solution also works with the constructor injector, but for fields you will need both @Inject and @AutoConfig(...) , and AutoConfig will need to save the @BindingAnnotation meta annotation.
To do this, you will have to write an implementation of your annotation, as Guice does with NamedImpl . Note that the equals and hashCode must match those Java provides in java.lang.Annotation . Then this is just a (redundant) question related as follows:
for(Class<ConfigLoader<?>> clazz : loaders) { bind(ConfigLoader.class).annotatedWith(new AutoConfigImpl(clazz)) .toProvider(clazz); }
The definition of equals up to you, which means you can (and should) bind @AutoConfig(ConfigEnum.JSON) and store Guice bindings in your modules, rather than specifying the requested implementation throughout your code base.
Use custom injections
You can also use custom injections to search for typed types for custom annotations like @AutoConfig . At this point, you will use Guice as the platform for interpreting @AutoConfig instead of @Inject , which means the constructor installation will not work, but you can control your injection based on the entered instance, field, annotation field, annotation parameters, or any combination of them . If you choose this style, you can remove @BindingAnnotation from AutoConfig.
Use the example in the wiki article above as a template, but at a minimum you need to:
- Use a
bindListener in a Binder or AbstractModule to match the types that this custom injection needs. - In the TypeListener that you are binding, search for the entered types for
@AutoConfig occupied fields, and if they have any matching methods, bind these matching methods to MemberInjector or InjectionListener. You will probably want to tease the class literal from the annotation instance here and pass in the field and class as constructor arguments to MemberInjector / InjectionListener. - In MemberInjector or InjectionListener, you write, create an instance of the provider, and set the field to the instance provided by the provider.
This is a very powerful feature that allows you, for example, to automatically provide a configuration based on which instance you enter or based on the field name. However, use it carefully and document it, because it may not be reasonable for your colleagues that Guice provides an annotation other than @Inject . Also keep in mind that this will not work for constructor injection, so refactoring from field injection to constructor injection will cause Guis to complain that he does not have a binding to create an instance of the class.