Guice - 在运行时根据定义的注入点将泛型 class 绑定到实例

Guice - Bind generic class to instance at runtime based on defined InjectionPoints

我有一个 class Property<T>,我想在我的应用程序启动后绑定它。 Property<T> 表示类型 T 的 属性,其值可以在运行时修改。

我有 classes 可以这样注入:

public class MyClass {
    public MyClass(@Named("someName") Property<String> property) {
       ...
    }
}

我可以绑定这些实例,但只有在我的应用程序启动后我才需要知道所有 Named 注释值才能这样做。


我已经开始查看 Elements 并访问所有 Element 实例以查找其所有 Binding。一旦我有了 Bindings,我就可以使用 InjectionPoint.forConstructor() 来获取构造函数。

困扰我的是各种类型的绑定都有其差异。我必须处理访问 LinkedKeyBindingUntargettedBinding 等。 有没有更简单的方法来获取给定模块列表的所有 InjectionPoint ?或许我做错了?

谢谢!

听起来您正试图为所有 foo 和 bar 注入 @Named(foo) Property<bar> 并在事后解析它们,Guice 做得不是特别好。因为 Guice 配置发生在 运行 时间(configure 方法必须 运行),所以您可以使用 Guice 的 SPI 进行您正在做的事情的唯一方法是先发制人地绑定所有键这样 Guice 就不会抱怨对象图不完整 然后 检查它以确定要填充的内容。这似乎费力且设计过度。

此外,只要 Guice 允许 JIT 或 "implicit" 绑定,在请求对象之前,您无法知道 Guice 可能尝试解析的每个键。这可能会让你很难绑定你需要的一切,即使你有一个完美的模块或注入器反射库。如果您在注入器创建之前不知道您需要什么或什么可用,您也会绕过 Guice 的一些依赖图验证功能。

虽然这不是 Guice 和依赖注入概念的完美使用,但我会通过编写 PropertyOracle:

来调整我的代码
public class MyClass {
    private final Property<String> someNameProperty;

    public MyClass(PropertyOracle propertyOracle) {
        someNameProperty = propertyOracle.getString("someName");
        // The act of getting tells the oracle which properties to get. You can also
        // separate those steps without holding onto an instance:
        propertyOracle.prepare("keyToBeRequestedLater");
    }
}

尽管这种类型的规定在某种程度上重复了 Guice,但与 Guice 不同的是,您可以使用任何类型或所需的键注入 属性,然后稍后解析它——可能是异步的。然后你会写一个假的或模拟的 属性Oracle 在测试中使用,这不像直接注入 属性 实例那么容易,但它可能是最简单的挂钩请求的方法Guice SPI的并发症

让我们假设注释是声明性的,并且有一个最终的 Property 注入计数。因此,您可以在应用程序启动时扫描它并创建所有绑定。

我会创建一个自定义注释 - 假设 Configuration 带有 Property 字段(它可以只是值)。 我会为配置注释创建一个 class 路径扫描器,并为每个 Configuration.

注册一个 Provider

然后看起来像:

您的绑定:

public class MyClass {
    public MyClass(@Configuration("someName") Property<String> property) {
       ...
    } }

你的模块:

ClasspathScanner classpathScanner = new ClasspathScanner(
    Arrays.asList("com.example"),
    Lists.newArrayList());

    List<Configuration> configurations = classpathScanner.getClasses().
        stream().
        filter(c -> find consturctors with Configuration annotation).
        collect(Collectors.toList());

    configurations.forEach(e -> bind(Key.get(String.class,e)).toProvider(new PropertyStringProvider(e.value())));

您的提供商:

public class PropertyStringProvider implements Provider<String> {

  private final String propertyName;      

  public PropertyStringProvider(String propertyName) {
    this.propertyName = propertyName;
  }

  @Override
  public String get() {
    return //find property for given name. Best to use Archaius framework
  }
}

好吧,这个提案的灵感来自于基于guice的Governator框架。 https://github.com/Netflix/governator/wiki/Configuration-Mapping

我最终使用了 BindingTargetVisitor,它涵盖了我需要的用例。

请注意,此解决方案适用于我们的特定用例,但对于您的用例而言可能过于有限,具体取决于您使用的绑定和注入类型。

public class InjectionPointExtractor extends DefaultBindingTargetVisitor<Object, InjectionPoint> {
  private final Predicate<TypeLiteral<?>> filter;

  public InjectionPointExtractor(Predicate<TypeLiteral<?>> filter) {
    this.filter = filter;
  }

  @Override
  public InjectionPoint visit(UntargettedBinding<?> untargettedBinding) {
    return getInjectionPointForKey(untargettedBinding.getKey());
  }

  @Override
  public InjectionPoint visit(LinkedKeyBinding<?> linkedKeyBinding) {
    return getInjectionPointForKey(linkedKeyBinding.getLinkedKey());
  }

  @Override
  public InjectionPoint visit(ProviderKeyBinding<?> providerKeyBinding) {
    return getInjectionPointForKey(providerKeyBinding.getProviderKey());
  }

  private InjectionPoint getInjectionPointForKey(Key<?> key) {
    if (filter.test(key.getTypeLiteral())) {
      return InjectionPoint.forConstructorOf(key.getTypeLiteral());
    }

    return null;
  }
}

我们使用 filter 仅过滤我们包中定义的 类。这样可以以一种干净整洁的方式完成工作。

如果您不使用 @Inject 构造函数而是使用 Guice 直接设置字段,则可以使用 TypeListener