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.
解决方案
如果您使用 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
如果有必要,你可以恢复。
推荐阅读
- angular - 如何使用 node js buildpack 在 Cloud Foundry 上部署 Angular 6 应用程序?
- node.js - 在 Nodejs 和 MongoDB 的单元测试中使用fixtures
- amazon-web-services - 如何检查第三方 AMI 在我的 AWS 账户中运行?
- ios - 如何减小ipa文件的大小
- r - 在 R 中跟踪具有滞后的属性
- python - 使用 colorsys 模块在 Mandelbrot 和 Julia 集上产生彩虹效果
- r - 有条件地选择 dplyr 中的列
- sql - 使用左连接加入 3 个表
- python - 从数据框中的索引中获取最小值
- c++ - 无法将多个键添加到以结构为键的映射