如何使用 Guice 注入器创建对象?

How to create objects with Guice injectors?

我的代码中有:

private static class BaseScriptInfoParser extends NodeParser<Asset.ScriptInfo> {

    private Asset.ScriptKindEnum scriptKind;

    private final NodeParser<Asset.TransformerKindEnum> transformerKindNodeParser;
    private final NodeParser<Asset.ValidatorKindEnum>   validatorKindNodeParser;

    @Inject
    BaseScriptInfoParser(
            // First two arguments are injectors
            @Named("transformerKind") NodeParser<Asset.TransformerKindEnum> transformerKindNodeParser,
            @Named("validatorKind") NodeParser<Asset.ValidatorKindEnum> validatorKindNodeParser,
            Asset.ScriptKindEnum scriptKind)
    {
        this.scriptKind = scriptKind;
        this.transformerKindNodeParser = transformerKindNodeParser;
        this.validatorKindNodeParser   = validatorKindNodeParser;
    }

    // ...

}

现在我想创建一个 BaseScriptInfoParser 的实例而不像这样的代码(因为据我了解它应该只在 Main 函数中)

Injector injector = Guice.createInjector(new BoilerModule());
// ...

我可以只调用一个构造函数来创建一个 BaseScriptInfoParser class 的对象,带有一个参数(Asset.ScriptKindEnum 类型)并自动注入前两个参数吗?

或者如何使用注入器创建对象?

如果构造函数 BaseScriptInfoParser 没有第三个参数,它会如何工作?

避免额外调用 createInjector 并避免传递您创建的 Injector 是正确的。对于最佳 Guice 实践,您应该准确指定任何给定组件创建或依赖的对象,而 Injector 恰恰相反:它可以创建任何东西。

相反,一般来说,您应该从图中注入您需要的对象。如果你认为你以后可能只需要一个对象,或者你可能需要一个对象的多个实例,你可以注入一个 Provider<T> 代替(其中 T 是图中可用的任何对象),然后你可以请求实例稍后就好像您调用了 getInstance (但没有创建新对象或使图形的其余部分可用)。这也应该使测试更容易,因为 simulating a Provider in tests is very easy,但是模拟 Injector 很困难,使用真实的 Injector 既昂贵又复杂。

如果 BaseScriptInfoParser 没有这个手动的第三个参数,你可以只注入 Provider<BaseScriptInfoParser>:Guice 会自动处理这个只要 BaseScriptInfoParser 有一个 public 无参数构造函数,一个@Inject 带注释的构造函数,或模块中的 bind(BaseScriptInfoParser.class) 绑定或 @Provides BaseScriptInfoParser 方法。


现在,关于混合注入构造函数参数和非注入参数:

并非图表中的每个对象都需要是可注入的:要使用 Miško Hevery 的 "To new or not to new" article 术语,您的应用程序可能由 injectables 组成,它们来自该图,其中一些 newables 像 "value objects" 和 "data objects" 有很多状态并且没有依赖关系。

但是,对于某些对象,构造函数提供的不可变状态同时从图中访问可注入对象是有意义的,而不将两者分离为单独的对象(这也是一个选项)。实际上,您想要的是您的 DI 框架可以提供的对象,它实现了这个接口:

interface BaseScriptInfoParserFactory {
  /**
   * Calls the BaseScriptInfoParser constructor, with other constructor params
   * injected from the graph.
   */
  BaseScriptInfoParser create(Asset.ScriptKindEnum scriptKind);
}

因为这是一个非常明确的 class 编写,Google 提供了几个不同的选项来自动生成一个:您可以使用 Guice 的反射 Assisted Injection or AutoFactory from the code-generating Google Auto包裹。后者更快一些,因为它生成普通代码而不是运行时反射代码,但前者与 Guice 的集成稍微好一点:

  1. 确保 Guice 辅助注入 JAR 在 class 路径上。是分开的。

  2. 标记您的构造函数以说明哪些参数应来自 Guice:

    @Inject BaseScriptInfoParser(
          @Named("transformerKind") NodeParser<...> transformerKindNodeParser,
          @Named("validatorKind") NodeParser<...> validatorKindNodeParser,
          @Assisted Asset.ScriptKindEnum scriptKind)
    
  3. 写一个你可以注入的工厂接口,我喜欢做一个嵌套接口:

    public class BaseScriptInfoParser {
      public interface Factory {
        // Any interface and method name works. These are the most common.
        BaseScriptInfoParser create(Asset.ScriptKindEnum scriptKind);
      }
      // ... rest of the class, including the above constructor
    }
    
  4. 告诉 Guice 编写一个实现并绑定到它:

    public class YourModule extends AbstractModule {
      @Override public void configure() {
        install(new FactoryModuleBuilder()
            .build(BaseScriptInfoParser.Factory.class));
      }
    }
    
  5. 注入您的 BaseScriptInfoParser.Factory 并在需要新对象时调用 create(someScriptKind)