首页 > 解决方案 > 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.

标签: guice

解决方案


如果您使用 Multibinder 进行地图绑定,那么您可以使用 MapBinder 将每个 Shipper 实例绑定到地图

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);

然后在你注入的类中,你可以注入一个Map<String, Provider<ShipperInterface>>

private ShipperInterface FindShipperInterface(String 
    preferredShipperAbbreviation) {

  ShipperInterface foundShipperInterface =
      providerMap.get(preferredShipperAbbreviation).get();
}

您也可以Map<String, ShipperInterface>直接注入一个,但 Multibinder 可以免费处理 Provider 间接寻址,这样您就可以避免在实际上只需要一个时创建三个 ShipperInterface 实例。此外,如果您的实例选择代码比简单地从您在编译时知道的一组实现中选择一个字符串更复杂,您可能仍然需要您编写的工厂实现


作为旁注,理想情况下使用@Inject注释而bind(...).to(...)不是toConstructor. 这不会将您绑定到 Guice,因为@Inject它是在 JSR-330 中定义的,并且您正在添加您可以选择以后不使用的注释。您还可以@Provides在 AbstractModule 中编写一个方法,就像这样,它并不比您的toConstructor绑定更脆弱:

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

toConstructor当且仅当您使用遗留代码、不受控制的代码、非常严格的代码样式规则或 AOP(此处可能就是这种情况)时才使用。为了一个简洁的例子,我已经在上面这样做了,但toConstructor如果有必要,你可以恢复。


推荐阅读