Search code examples
guice

Guice with multiple concretes......picking one of them


I am injection multiple concretes of the same interface.

I figured out the Guide "code it up" convention.

My code currently spits out

[INFO] App - About to ship. (abc)
[INFO] App - ShipperInterface . (FedExShipper)
[INFO] App - ShipperInterface . (UpsShipper)
[INFO] App - ShipperInterface . (UspsShipper)

So I have the multiple "shippers" at my fingertips.

Note the method:

public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {

I'm trying to figure out the best way to use the (String) preferredShipperAbbreviation to choose 1 of the 3 concrete shippers.

Is there a way to "name" my 3 concretes when I register them with Guice?

Or what is the best way to pick 1 of the three ?

public class ProductionInjectModule extends AbstractModule implements Module {

  @Override
  protected void configure() {
    try {
      bind(OrderProcessorInterface.class).toConstructor(OrderProcessorImpl.class.getConstructor(Set.class));

      Multibinder<ShipperInterface> multibinder = Multibinder.newSetBinder(binder(), ShipperInterface.class);
      multibinder.addBinding().toConstructor(FedExShipper.class.getConstructor(org.apache.commons.logging.Log.class));
      multibinder.addBinding().toConstructor(UpsShipper.class.getConstructor(org.apache.commons.logging.Log.class));
      multibinder.addBinding().toConstructor(UspsShipper.class.getConstructor(org.apache.commons.logging.Log.class));

    } catch (NoSuchMethodException e) {
      addError(e);
    }
  }

}

=============

import java.util.Collection;
import java.util.Set;

import org.apache.commons.logging.Log;


public class OrderProcessorImpl implements OrderProcessorInterface {

  private Log logger;
  Set<ShipperInterface> shippers;

  public OrderProcessorImpl(Log lgr, Set<ShipperInterface> shprs) {

    if (null == lgr) {
      throw new IllegalArgumentException("Log is null");
    }

    if (null == shprs) {
      throw new IllegalArgumentException("ShipperInterface(s) is null");
    }

    this.logger = lgr;
    this.shippers = shprs;
  }

  public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {
    this.logger.info(String.format("About to ship. (%1s)", preferredShipperAbbreviation));

    for (ShipperInterface sh : shippers) {
      this.logger.info(String.format("ShipperInterface . (%1s)", sh.getClass().getSimpleName()));
    }

  }
}

=============

public interface OrderProcessorInterface {

  void ProcessOrder(String preferredShipperAbbreviation, Order ord);

}

public class FedExShipper implements ShipperInterface {

  private Log logger;

  public FedExShipper(Log lgr) {

    if (null == lgr) {
      throw new IllegalArgumentException("Log is null");
    }

    this.logger = lgr;
  }

  public void ShipOrder(Order ord) {
    this.logger.info("I'm shipping the Order with FexEx");
  }
}


public class UpsShipper implements ShipperInterface {

  private Log logger;

  public UpsShipper(Log lgr) {

    if (null == lgr) {
      throw new IllegalArgumentException("Log is null");
    }

    this.logger = lgr;
  }

  public void ShipOrder(Order ord) {
    this.logger.info("I'm shipping the Order with Ups");
  }
}


public class UspsShipper implements ShipperInterface {

  private Log logger;

  public UspsShipper(Log lgr) {

    if (null == lgr) {
      throw new IllegalArgumentException("Log is null");
    }

    this.logger = lgr;
  }

  public void ShipOrder(Order ord) {
    this.logger.info("I'm shipping the Order with Usps");
  }
}

..............

"Main" method:

ProductionInjectModule pm = new ProductionInjectModule();
Injector injector = Guice.createInjector(pm);

Order ord = new Order();
OrderProcessorInterface opi = injector.getInstance(OrderProcessorInterface.class);
opi.ProcessOrder("WhatDoIPutHere?", ord);

=========== Guice version below:

    <dependency>
        <groupId>com.google.inject</groupId>
        <artifactId>guice</artifactId>
        <version>4.2.0</version>
    </dependency>

================================

One way I'm trying it this way. Is this as good as any way?

Ultimately, in my "real" scenario (not this made up one)......I want to keep the "concreteKey" as a database/configuration setting.

Order ord = new Order();
OrderProcessorInterface opi = injector.getInstance(OrderProcessorInterface.class);
opi.ProcessOrder(FedExShipper.class.getSimpleName(), ord);

  public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {
    this.logger.info(String.format("About to ship. (%1s)", preferredShipperAbbreviation));

    ShipperInterface foundShipperInterface = this.FindShipperInterface(preferredShipperAbbreviation);
    foundShipperInterface.ShipOrder(ord);
  }

  private ShipperInterface FindShipperInterface(String preferredShipperAbbreviation) {

    /* requires java 8 */
    ShipperInterface foundShipperInterface = this.shippers
        .stream().filter(x -> x.getClass().getSimpleName().equalsIgnoreCase(preferredShipperAbbreviation)).findFirst().orElse(null);

    if(null == foundShipperInterface)
    {
      throw new NullPointerException(String.format("ShipperInterface not found in ShipperInterface collection. ('%1s')", preferredShipperAbbreviation));
    }

    return foundShipperInterface;
  }

============= APPEND ==================

I got this to work thanks to Jeff B's answer/comments.

import java.util.Map;
import java.util.Set;

import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;

public class ProductionInjectModule extends AbstractModule implements Module {

  @Override
  protected void configure() {
    try {

      MapBinder<String, ShipperInterface> mappyBinder = MapBinder.newMapBinder(binder(), String.class, ShipperInterface.class);
      mappyBinder.addBinding("myFedExName").toConstructor(FedExShipper.class.getConstructor(org.apache.commons.logging.Log.class));
      mappyBinder.addBinding("myUPSName").toConstructor(UpsShipper.class.getConstructor(org.apache.commons.logging.Log.class));
      mappyBinder.addBinding("myUSPSName").toConstructor(UspsShipper.class.getConstructor(org.apache.commons.logging.Log.class));

        /* below is not needed, but shows what needs to be injected */
      java.util.Map<String,  javax.inject.Provider<ShipperInterface>> shipperProviderMap;


    } catch (NoSuchMethodException e) {
      addError(e);
    }
  }
}

================

import java.util.Collection;
import java.util.Set;

import org.apache.commons.logging.Log;

public class OrderProcessorImpl implements OrderProcessorInterface {

  private Log logger;
  private java.util.Map<String, javax.inject.Provider<ShipperInterface>> shipperProviderMap;

  public OrderProcessorImpl(Log lgr, java.util.Map<String, javax.inject.Provider<ShipperInterface>> spMap) {

    if (null == lgr) {
      throw new IllegalArgumentException("Log is null");
    }

    if (null == spMap) {
      throw new IllegalArgumentException("Provider<ShipperInterface> is null");
    }

    this.logger = lgr;
    this.shipperProviderMap = spMap;
  }

  public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {
    this.logger.info(String.format("About to ship. (%1s)", preferredShipperAbbreviation));


    ShipperInterface foundShipperInterface = this.FindShipperInterface(preferredShipperAbbreviation);
    foundShipperInterface.ShipOrder(ord);
  }

  private ShipperInterface FindShipperInterface(String preferredShipperAbbreviation) {

    ShipperInterface foundShipperInterface = this.shipperProviderMap.get(preferredShipperAbbreviation).get();

    if (null == foundShipperInterface) {
      throw new NullPointerException(
          String.format("ShipperInterface not found in shipperProviderMap. ('%1s')", preferredShipperAbbreviation));
    }

    return foundShipperInterface;
  }
}

================

"main" method

ProductionInjectModule pm = new ProductionInjectModule();
Injector injector = Guice.createInjector(pm);

Order ord = new Order();
OrderProcessorInterface opi = injector.getInstance(OrderProcessorInterface.class);
opi.ProcessOrder("myFedExName", ord); /* now use the "friendly named" strings */

OUTPUT:

[INFO] App - About to ship. (myFedExName)
[INFO] App - I'm shipping the Order with FexEx

I probably have some extra "logger" injections in my newly posted code.....but simple clean up would get it running.


Solution

  • If you use Multibinder for map bindings, then you could bind each of the Shipper instances into a Map using MapBinder:

    MapBinder<String, ShipperInterface> multibinder = MapBinder.newMapBinder(
        binder(), String.class, ShipperInterface.class);
    multibinder.addBinding("FedEx").to(FedExShipper.class);
    multibinder.addBinding("UPS").to(UpsShipper.class);
    multibinder.addBinding("USPS").to(UspsShipper.class);
    

    Then in your injected class you can inject a Map<String, Provider<ShipperInterface>>:

    private ShipperInterface FindShipperInterface(String 
        preferredShipperAbbreviation) {
    
      ShipperInterface foundShipperInterface =
          providerMap.get(preferredShipperAbbreviation).get();
    }
    

    You could also inject a Map<String, ShipperInterface> directly, but Multibinder handles the Provider indirection for free, which lets you avoid creating three ShipperInterface instances when only one will actually be necessary. Also, if your instance-selection code is more complicated than simply choosing based on a String from a set of implementations you know at compile time, you might still want a Factory implementation you write.


    As a side note, ideally use @Inject annotations and bind(...).to(...) instead of toConstructor. This doesn't tie you to Guice, because @Inject is defined in JSR-330, and you are adding annotations that you can choose not to use later. You can also write a @Provides method in your AbstractModule, like so, which is no more fragile than your toConstructor bindings:

    @Provides UspsShipper provideUspsShipper(Log log) {
      return new UspsShipper(log);
    }
    

    Use toConstructor if and only if you are using legacy code, code you don't control, very restrictive code style rules, or AOP (which may be the case here). I've done so above for the sake of a concise example, but you can revert to toConstructor if necessary.