Guice: One “Provider” for multiple implementati

2019-02-20 03:36发布

问题:

I have an interface that has 20 or so annotated implementations. I can inject the correct one if I know which I need at compile time, but I now need to dynamically inject one based on runtime parameters.

As I understood the documentation, I would have to use 20 or so Provider<T> injections and then use the one I need, which seems rather excessive to me. Is there a way to have something like an inst(Provider<T>).get(MyAnnotation.class) to bind a specific implementation, and then have only that Provider injected into my class?

回答1:

Inject a MapBinder.

In your module, load the bindings into the MapBinder, then make your runtime parameters injectable as well. This example is based on the one in the documentation:

public class SnacksModule extends AbstractModule {
  protected void configure() {
    MapBinder<String, Snack> mapbinder
           = MapBinder.newMapBinder(binder(), String.class, Snack.class);
    mapbinder.addBinding("twix").to(Twix.class);
    mapbinder.addBinding("snickers").to(Snickers.class);
    mapbinder.addBinding("skittles").to(Skittles.class);
  }
}

Then, in your object, inject the Map and the parameter. For this example I will assume you've bound a java.util.Properties for your runtime parameters:

@Inject
public MyObject(Map<String, Provider<Snack>> snackProviderMap, Properties properties) {
  String snackType = (String) properties.get("snackType");
  Provider<Snack> = snackProviderMap.get(property);

  // etc.
}

Note, with the same MapBinder you can inject either a simple Map<String, Snack> or a Map<String, Provider<Snack>>; Guice binds both.



回答2:

If all you want is to get an instance programmatically, you can inject an Injector. It's rarely a good idea--injecting a Provider<T> is a much better idea where you can, especially for the sake of testing--but to get a binding reflectively it's the only way to go.

class YourClass {
  final YourDep yourDep;  // this is the dep to get at runtime

  @Inject YourClass(Injector injector) {
    YourAnnotation annotation = deriveYourAnnotation();
    // getProvider would work here too.
    yourDep = injector.getInstance(Key.get(YourDep.class, annotation));
  }
}

If you're trying write a Provider that takes a parameter, the best way to express this is to write a small Factory.

class YourDepFactory {
  @Inject @A Provider<YourDep> aProvider;
  @Inject @B Provider<YourDep> bProvider;
  // and so forth

  Provider<YourDep> getProvider(YourParameter parameter) {
    if (parameter.correspondsToA()) {
      return aProvider;
    } else if (parameter.correspondsToB()) {
      return bProvider;
    }
  }

  YourDep get(YourParameter parameter) {
    return getProvider(parameter);
  }
}