1

My current situation:

I want to inject the following class into my application:

public interface IConfigAccessor<T extends IConfig> {
    ...
}

ConfigAccessors 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)

How I want to inject them via Guice:

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!
    }
}

What's the correct approach/technology to get this working?

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?


Edit: What I have so far

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:

  1. The provider cannot provide 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.
  2. Inside the provider, there is no way to find out about the 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.

maja
  • 17,250
  • 17
  • 82
  • 125
  • The duplicates have examples of all the features that will get you there. –  Feb 11 '18 at 16:08
  • @JarrodRoberson Thank you. If I understood the other questions (in particular https://stackoverflow.com/a/28575445/2224996) correctly, this is not possible with Guice. When I want to inject these objects inside a constructor, I need to implement a Provider. However, this is not possible because I wouldn't be able to get the 'configKey' from the annotation into the provider. When using Listeners, I effectively manage injection myself, but subsequently cannot use it within the constructor. Am I correct? – maja Feb 11 '18 at 16:30
  • @JarrodRoberson However, I cannot use a custom annotation either, because I wouldn't be able to construct the object-to-inject (`ConfigAccessor`) in the first place. Within the TypeListener, I cannot inject other objects from Guice (`IConfigUpdater `) – maja Feb 11 '18 at 16:36
  • @JarrodRoberson So in the end, it all boils down to "I cannot create the object-to-inject `ConfigAccessor` inside modules, because I would need the injector for that. And if I create the objects at runtime (after I have the injector), I cannot inject them themself anymore. – maja Feb 11 '18 at 16:42
  • You are hugely over complicating this. `Provider` is for injecting things that are not available inside the module, more specifically that require the `injector` to already be created. That covers every reasonable case I have ever come across since Guice first came out, 10 years ago! If it does not then you need to re-evaluate what you are doing. –  Feb 11 '18 at 16:57
  • @JarrodRoberson I updated the question and added my current state where I'm stuck. I'm not sure if it's really that simple to solve as you say it is (though I hope so) – maja Feb 11 '18 at 17:13
  • Let guice create the `IConfigUpdater` for you. Also, @Provides methods in your Module are usually simpler than all the bind() stuff. – giorgiga Feb 15 '18 at 10:14
  • Out of curiosity, have you worked with spring for any long time? :) – giorgiga Feb 15 '18 at 10:15
  • @giorgiga Yes, I did work with Spring for some time, but it's already a while ago. Why? – maja Feb 19 '18 at 14:58

1 Answers1

1

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.

kutschkem
  • 7,826
  • 3
  • 21
  • 56