maja
maja

Reputation: 18034

Dynamically injecting generic objects with guice

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.

Upvotes: 1

Views: 1410

Answers (1)

kutschkem
kutschkem

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

Related Questions