Search code examples
dependency-injectionguiceguice-3

How to inject a list with different implementations of the same interface in a nested module scenario via Guice?


There is an interface DCE, which is implemented by a class DCEImpl which has a dependency, say, string S, which it gets via its constructor.

The universe of S is limited, say S can only take values {'A','B','C'}.

There is an already existing Guice module that accepts the value of S in its constructor, and then binds the interface DCE to the correctly initialized version of DCEImpl.

public class DCEModule extends AbstractModule {
    private final String s;
    public DCEModule(String s){
       this.s = s;
    }
    protected void configure() {
      bind(DCE.class).toInstance(new DCEImpl(s));
    }
}

Now I have a class C which needs a List<DCE> with all the 3 implementations (actually a lot more than 3, using 3 for example purpose).

I want to inject this list via Guice in C. To do that, I created a new module DCEPModule, which will provide a List<DCE> in this way:

@Provides
List<DCE> getDCE() {
      for(String s: S){
            Module m = new DCEModule(s);
            install(m);
            Injector injector = Guice.createInjector(m);
            listDomains.add(injector.getInstance(DCE.class));
        }
}

My problem is that I don't want to call a new injector in this module, because DCEPModule will be installed by a different module.

public class NewModule extends AbstractModule {
   protected void configure() {
      install(DCEPModule);
    }

}

I want a way to get the List<DCE> without explicitly creating a new injector in DCEPModule.


Solution

  • You can achieve this by using a Multibinder (javadoc, wiki). Here’s an example:

    public class SnacksModule extends AbstractModule {
        protected void configure(){
             Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
             multibinder.addBinding().toInstance(new Twix());
             multibinder.addBinding().toProvider(SnickersProvider.class);
             multibinder.addBinding().to(Skittles.class);
        }
    }
    

    Now, the multibinder will provide a Set<Snack>. If you absolutely need a List instead of a Set, then you can add a method to your module like this:

    @Provides
    public List<Snack> getSnackList(Set<Snack> snackSet) {
        return new ArrayList(snackSet);
    }
    

    You can add implementations to the same Multibinding in more than one module. When you call Multibinder.newSetBinder(binder, type) it doesn’t necessarily create a new Multibinding. If a Multibinding already exists for for that type, then you will get the existing Multibinding.