Guice 中的绑定:编译时间依赖

Bindings in Guice: Compile time dependencies

在探索 Guice 时,我对注入依赖项的方式有疑问。

根据我的理解,DI 的重要方面之一是,依赖项是已知的并在运行时注入。

在 Guice 中,要注入依赖项,我们需要添加绑定或实现提供程序。添加依赖项需要一个 class 对象,该对象会在 class 上添加编译时依赖项。避免这种情况的一种方法是将其实现为提供者并让提供者使用反射来动态加载 class。

public class BillingModule extends AbstractModule {

@Override
protected void configure() {
    bind(CreditCardProcessor.class).toProvider(
            BofACreditCardProcessorProvider.class);
    bind(CreditCardProcessor.class).annotatedWith(BofA.class).toProvider(
            BofACreditCardProcessorProvider.class);
    bind(CreditCardProcessor.class).annotatedWith(Amex.class).toProvider(
            AmexCreditCardProcessorProvider.class);
}

@Provides
PaymentProcessor createPaymentProcessor() {
    return new PayPalPaymentProcessor();
}

@Provides
PayPalPaymentProcessor createPayPalPaymentProcessor() {
    return new PayPalPaymentProcessor();
}}

Guice 选择 class 对象而非 class 名称是否有原因?那可以删除编译时依赖吗?

如果你的interfaceimplementation是在同一个依赖项中定义的(也就是说,在同一个JAR文件中)那么你已经有对实现的硬构建依赖,无论你是否使用 Guice。

基本上,只要您拥有:

public final class MyClass {
  public void doSomething(Foo foo);
}

然后编译 MyClass Foo 的定义需要在编译时类路径上。

解决这个问题的方法是将接口从实现中分离出来。例如,如果 Foo 是一个接口,而 FooImpl 是它的实现,您可以将 FooImpl 放在与 [=15 不同的依赖项(即不同的 JAR 文件)中=].

现在,假设您在 Maven 中有两个子项目:

foo-api/
  pom.xml
  src/main/java/com/foo/Foo.java

foo-impl/
  pom.xml
  src/main/java/com/foo/FooImpl.java

绑定Foo的Guice模块应该放在哪里?它不应该存在于 foo-api 项目中,它应该存在于 foo-impl 项目中,与 FooImpl.

一起

现在假设您有一个单独的 Foo 实现(我们称之为 SuperFoo),并且您的项目需要一个 Foo,但它可以是 FooImplSuperFoo.

如果我们将 SuperFoo 作为自己的项目:

super-foo/
  pom.xml
  src/main/java/com/super/foo/SuperFoo.java
  src/main/java/com/super/foo/SuperFooModule.java

现在您的所有应用程序代码都可以简单地 @Inject Foo 并使用 foo.在您的 main() 方法中(或您创建 Injector 的任何地方),您需要决定是安装 FooModule(来自 foo-impl)还是 SuperFooModule(来自 super-foo).

那个是值得反思的地方。例如,您可以有一个配置标志 foo_module,它可以设置为 "com.foo.FooModule""com.super.foo.SuperFooModule"。您可以使用如下代码决定安装哪一个:

public static void main(String[] args) {
  Config config = parseConfig(args);
  List<Module> modules = new ArrayList<>();
  modules.add(...); // application modules

  String fooModuleName = config.get("foo_module");
  Class<? extends Module> moduleClass =
      Class.forName(fooModuleName).asSubclass(Module.class);
  modules.add(moduleClass.newInstance());

  Injector injector = Guice.createInjector(modules);
  injector.getInstance(MyApplication.class).run();
}

当然,您也可以使用您喜欢的任何其他机制来select安装哪个模块。在许多情况下,您甚至不想反思地这样做,您可以在更改构建依赖项的同时简单地更改代码。