I would like to use goolge/guice inject a value based on a class i provide with the annotation.
AutoConfig annotation
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER, ElementType.FIELD })
public @interface AutoConfig {
// default null not possible
Class<? extends Provider<? extends ConfigLoader<?>>> provider() default XMLAutoConfigProvider.class;
}
This is my annotation which allows configuring the type of config, that should be used for the annotated fields.
Usecase:
@AutoConfig()
ConfigLoader<?> defaultConfig;
@AutoConfig(provider = JsonConfigProvider)
ConfigLoader<?> jsonConfig;
I want to have two configs, one default/xml one and a json one. They will probably never occur in the same class at the same time. But i don't know when the one or the other is used. I used the approach with a class because they are provided by some dependencies/libs and this annotation will be used for some (plugable) submodules.
MyGuiceModule
public class MyGuiceModule extends AbstractModule {
@Override
protected void configure() {
bind(new TypeLiteral<ConfigLoader<?>>() {})
.annotatedWith(AutoConfig.class)
.toProvider(autoConfig.provider());
}
}
This the critical part, i just cannot imagine how to implement it.
So basically i just want to use the provider class specified in the annotation. Its not necessary to use the provider class here too. Because autoConfig.provider().newInstance() is basically all i need. (I need to use a setter on the new instance but thats all i want to do at this place)
To sum it up all i really want to do is push the annotation (or its values to the provider) either using the get(AutoConfig autoConfig) or in the constructor. Currently i only use the constructor to inject the configFile value i want to set on the newly generated config instance.
Note that the only Guice-specific code in the above is the @Inject annotation. This annotation marks an injection point. Guice will attempt to reconcile the dependencies implied by the annotated constructor, method, or field.
Using GuiceIn each of your constructors that need to have something injected in them, you just add an @Inject annotation and that tells Guice to do it's thing. 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.
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 is an open source, Java-based dependency injection framework. It is quiet lightweight and is actively developed/managed by Google. This tutorial covers most of the topics required for a basic understanding of Google Guice and to get a feel of how it works.
If you know that @AutoConfig(provider = JsonConfigProvider) ConfigLoader<?> jsonConfig
is going to return you exactly the results of jsonConfigProvider.get()
, and JsonConfigProvider obviously has a public parameterless constructor for newInstance
to work, why wouldn't you just ask for a JsonConfigProvider
in the first place?
Fundamentally Guice is just a Map<Key, Provider>
with fancy wrapping. The bad news is that this makes variable bindings like "bind Foo<T>
for all T" impossible to express concisely, and that includes your "bind @Annotation(T) Foo
for all T". The good news is that you still have two options.
Though you can't inspect annotations during provision (or tell Guice to do so for you), Guice will compare annotations using their equals
methods if you bind an annotation instance rather than an annotation class (the way you would with Names.named("some-name")
). This means that you can bind a ConfigLoader<?>
with each expected annotation in a Module. Of course, this also means you'll have to have a list of possible ConfigLoader Providers available at configuration time, but they have to be compile-time constants anyway if you're using them as annotation parameters.
This solution works with constructor injection as well, but for fields you'll need both @Inject
and @AutoConfig(...)
, and AutoConfig will need to keep its @BindingAnnotation
meta-annotation.
To do this, you're going to have to write an implementation of your annotation, the way Guice does with NamedImpl
. Note that the implementations of equals
and hashCode
must match the ones Java provides in java.lang.Annotation
. Then it's just a matter of (redundantly) binding like this:
for(Class<ConfigLoader<?>> clazz : loaders) {
bind(ConfigLoader.class).annotatedWith(new AutoConfigImpl(clazz))
.toProvider(clazz);
}
The definition of equals
is up to you, which means you can (and should) bind @AutoConfig(ConfigEnum.JSON)
and keep the Guice bindings in your modules rather than specifying your requested implementation all over your codebase.
You can also use custom injections to search your injected types for custom annotations like @AutoConfig
. At this point, you'd be using Guice as a platform to interpret @AutoConfig
instead of @Inject
, which means that constructor injection won't work but that you can control your injection based on the injected instance, field name, field annotation, annotation parameters, or any combination thereof. If you choose this style, you can drop @BindingAnnotation
from AutoConfig.
Use the example in the wiki article linked above as your template, but at minimum you'll need to:
bindListener
on Binder or AbstractModule to match types that need this custom injection.@AutoConfig
-annotated fields, and if they have any matching methods then bind those matching methods to a MembersInjector or InjectionListener. You'll probably want to tease the class literal out of the annotation instance here, and pass in the Field and Class as constructor arguments to the MembersInjector/InjectionListener.This is a very powerful feature, which would futher allow you to--for instance--automatically provide the configuration based on which instance you're injecting into or based on the name of the field. However, use it carefully and document it heavily, because it may be counter-intuitive to your coworkers that Guice is providing for an annotation other than @Inject
. Also bear in mind that this won't work for constructor injection, so refactoring from field injection to constructor injection will cause Guice to complain that it's missing a required binding to instantiate the class.
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