Guice : Inject an ArrayList of Strings

4.6k views Asked by At

I'm trying to inject an ArrayList of Strings with the help of Guice. I want to show a panel with many RadioButtons (for example) where an user can select some services to activate.

Once selected, I would like to get all the names of the selected services and add them into a list, and inject this list to the manager responsible to create the services. Here is an example:

public class UIProviderModule extends ProviderServiceModule {
    private ArrayList<String> requestedServices;

    public UIProviderModule(ArrayList<String> requestedServices) {
        this.requestedServices = requestedServices;
    }

    @Override
    protected void configure() {
        bindConstant().annotatedWith(Names.named(Constants.REQ_SERVICES)).to(requestedServices);
        bind(IParser.class).to(UIParser.class);
        super.configure();
    }

}

I've seen many posts about Multibindings and also about Providers, but I did not understand how this could help me. I just want to retrieve names, since I'm not working with classes that have to be bound to an interface. Am I missing something?

Note: I know this is maybe not the good way to use Guice because I'm giving the list to be bound to the Module.

2

There are 2 answers

2
durron597 On BEST ANSWER

I think you are misunderstanding how modules are supposed to work.

Modules don't create the objects, modules define rules for how objects might be created when they are needed.

The reason MapBinder would help is that you would define all of the services in your radio buttons list, and then use the injected map to activate the services that you need.

Here's some code to illustrate what I mean:

public class ServiceModule extends AbstractModule {
  protected void configure() {
    MapBinder<String, Service> mapbinder
        = MapBinder.newMapBinder(binder(), String.class, Service.class);
    mapbinder.addBinding("service1").to(Service1.class).in(Singleton.class);
    mapbinder.addBinding("service2").to(Service2.class);
    // Define ALL the services here, not just the ones being used.
    // You could also look this up from a ClassLoader or read from a configuration file if you want
  }
}

Then, inject the MapBinder to your ServiceManager class - which is not a module:

public class ServiceManager {
  private final Map<String, Service> serviceMap;

  @Inject
  public ServiceManager(Map<String, Service) serviceMap) {
    this.serviceMap = serviceMap;
  }

  // This is just one way to do it. It depends on how your UI works
  public void startAll(List<String> serviceList) {
    for(String serviceName : serviceList) {
      serviceMap.get(serviceName).start();
    }
  }
}
2
Jeff Bowman On

This is easy to do Guice:

bind(new TypeLiteral<List<String>>() {})
    .annotatedWith(Names.named(Constants.REQ_SERVICES))
    .toInstance(requestedServices);

Note that in order to bind a List<String> without Java erasing the generics, you create a short-lived anonymous inner type (a subclass of TypeLiteral with empty body {}). You also use toInstance.

There's nothing wrong with having a module take parameters that are used for binding, and I prefer to do so over Multibindings when all of the bindings are easily collected in one place.

Take care to note that the ArrayList<String> you accept is mutable, so if you inject this in more than one place, one consumer could change the list permanently for everyone else. It may make more sense to use Guava's ImmutableList.copyOf(list) or Collections.unmodifiableList(list) (though the latter would still let the list change if the Module creator changes the passed-in list later).


Regarding your proposed application lifecycle, remember that Guice's bindings should stay more-or-less constant after injector creation. The lifecycle you describe could make sense a couple of ways:

  • Show your dialog without Guice's help, then create an Injector with the selected options
  • Inject your List<String> of all options, show the dialog, and then pass around the list
  • Inject your List<String> with all options, show the dialog, and then create a child injector containing your list of selected options

All of those, however, are feasible depending on how accessible you want the full list and selected list to be in your application.