【问题标题】:Guice with multiple concretes......picking one of themGuice 与多个混凝土......挑选其中一个
【发布时间】:2018-09-11 15:30:31
【问题描述】:

我正在注入同一接口的多个具体。

我想出了指南“编码”的约定。

我的代码目前吐出

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

所以我有多个“托运人”在我的指尖。

注意方法:

public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {

我正在尝试找出使用(字符串)preferredShipperAbbreviation 选择 3 个具体托运人中的 1 个的最佳方式。

当我在 Guice 注册 3 个混凝土时,有没有办法“命名”它们?

或者从三个中选择一个的最佳方法是什么?

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

.......

“主要”方法:

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

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

============ Guice 版本如下:

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

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

我正在尝试这种方式。这和任何方式一样好吗?

最终,在我的“真实”场景中(不是这个虚构的)......我想将“concreteKey”保留为数据库/配置设置。

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 ==================

感谢 Jeff B 的回答/cmets,我得到了这个工作。

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

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

“主要”方法

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 */

输出:

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

我的新发布的代码中可能有一些额外的“记录器”注入......但简单的清理就会让它运行。

【问题讨论】:

标签: guice


【解决方案1】:

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

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&lt;String, Provider&lt;ShipperInterface&gt;&gt;:

private ShipperInterface FindShipperInterface(String 
    preferredShipperAbbreviation) {

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

您也可以直接注入Map&lt;String, ShipperInterface&gt;,但 Multibinder 会免费处理 Provider 间接寻址,这样您就可以避免在实际上只需要一个时创建三个 ShipperInterface 实例。此外,如果您的实例选择代码比简单地从您在编译时知道的一组实现中选择一个字符串更复杂,you might still want a Factory implementation you write


作为旁注,ideally use @Inject annotations and bind(...).to(...) instead of toConstructor。这不会将您绑定到 Guice,因为 @Inject 是在 JSR-330 中定义的,并且您正在添加您可以选择以后不使用的注释。你也可以在你的 AbstractModule 中写一个@Provides 方法,就像这样,它并不比你的toConstructor 绑定更脆弱:

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

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

【讨论】:

  • 谢谢。我现在正在尝试这个。至于@Annotation,我想我是一个“DI 纯粹主义者”......并且不认为任何地方都应该需要注释(c# 中的属性)。 Microsoft Unity“通过编码进行配置”不要求任何注释......并且它默认使用 ~most arguments 构造函数~作为 DI 的“go to”构造函数。 (它可以用属性覆盖(java中的注释)......但默认模式是使用最参数构造函数)(对我来说有点意义)。无论如何。只是思考的食物。
  • 我喜欢 Guice(在春天)....但希望它像 Unity 一样具有这种“默认为大多数参数构造函数”的功能。但我认为成为“纯粹主义者”的人数很少。
  • 嗨。 “MapBinder”的完整命名空间/包名称是什么?
  • MapBinder 是 Multibindings 系列类之一;它是 com.google.inject.multibindings.MapBinder。 (我把上面的结构弄错了,对不起!)我强烈不同意“默认最多的论点”是“纯粹的”行为。如果有的话,它倾向于假设而不是约定或配置:您注入的更新依赖项可能会导致模糊的破坏。在任何情况下,Guice 都是基于注释的,而 Dagger 更是如此,因此惯用的使用需要注释。
  • 哈哈!我知道我是少数。我不喜欢我的混凝土上的任何(at)注释。但我正在热身“(at)Inject 是在 JSR-330 中定义的”(我第二次看到这个)......因为它不是特定于框架的。 (at)Autowire 让我颤抖。我现在正在尝试更新的代码。谢谢。
猜你喜欢
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
  • 2013-01-09
  • 2017-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多