Reputation: 18034
I want to inject the following class into my application:
public interface IConfigAccessor<T extends IConfig> {
...
}
ConfigAccessor
s are a proxy-objects, created dynamically at runtime. The creation of these object works as follows:
public class ConfigFactory implements IConfigFactory {
private final IConfigUpdater updater;
@Inject
public ConfigFactory(IConfigUpdater updater) {
this.updater = updater;
}
@Override
public <T extends IConfig> IConfigAccessor<T> register(final String configKey, final Class<T> configClass) {
ConfigCache<T> configCache = new ConfigCache<>(new SomeOtherThings(), configKey, configClass);
updater.register(configCache);
return new ConfigAccessor<>(configCache, configKey, configClass);
}
}
As you can see, to create these objects, I need to inject the ConfigUpdater
and other depdencies. This means, that guice needs to be fully configured already.
To get the instance out of Guice, I use the following code:
IConfigFactory configClient = injector.getInstance(IConfigFactory.class);
IConfigAccessor<ConcreteConfig> accessor = configClient.register("key", ConcreteConfig.class)
Currently, I can get the requried objects, but I have to manually pass them around in my application.
Instead, what I want to have is the following:
public class SomeClass {
@Inject
public SomeClass(@Config(configKey="key") IConfigAccessor<ConcreteConfig> accessor) {
// hurray!
}
}
After a lot of research, I'm feeling a bit lost on how to approach this topic. There are a lot of different things Guice offers, including simple Providers
, custom Listeners
which scan classes and identify custom annotations, FactoryModuleBuilders
and more.
My problem is quite specific, and I'm not sure which of these things to use and how to get it working. I'm not even sure if this is even possible with Guice?
I have the following annotation which I want to use inside constructor paramters:
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectConfig {
String configKey();
}
Inside the module, I can bind a provider to IConfigAccessor (with the above annotation) as such:
bind(IConfigAccessor.class).annotatedWith(InjectConfig.class)
.toProvider(new ConfigProvider<>());
However, there are two problems whith this:
IConfigAccessor
. To create such an instance, the provider would need an IConfigUpdater
, but since I use 'new' for the provider, I can't inject it.configKey
used in the Annotation.Second approach:
Let's assume that I already know all configurations and configKeys I want to inject during startup. In this case, I could loop over all possible configKeys and have the following binding:
String configKey = "some key";
final Class<? extends IConfig> configClass =...;
bind(IConfigAccessor.class).annotatedWith(Names.named(configKey))
.toProvider(new ConfigProvider<>(configKey, configClass));
However, problem (1) still resides: The provider cannot get an IConfigUpdater
instance.
Upvotes: 1
Views: 1410
Reputation: 8163
The main problem here is that you cannot use the value of the annotation in the injection. There is another question which covers this part: Guice inject based on annotation value
Instead of binding a provider instance, you should bind the provider class, and get the class by injecting a typeliteral.
That way, your config factory can look like that:
public class ConfigFactory<T extends IConfig> implements IConfigFactory {
@Inject private final IConfigUpdater updater;
@Inject private TypeLiteral<T> type;
@Override
public IConfigAccessor<T> register(final String configKey) {
Class<T> configClass = (Class<T>)type.getRawType();
ConfigCache<T> configCache = new ConfigCache<>(new SomeOtherThings(), configKey, configClass);
updater.register(configCache);
return new ConfigAccessor<>(configCache, configKey, configClass);
}
}
And then SomeClass:
public class SomeClass {
@Inject
public SomeClass(ConfigFactory<ConcreteConfig> accessor) {
ConcreteConfig config = accessor.register("key");
}
}
Since SomeClass needs to know "key" anyway, this is not too much a change information-wise. The downside is that the SomeClass API now gets a factory instead of the concrete config.
[EDIT]
And here is someone who actually did inject annotated values using custom injection.
Upvotes: 1