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()
来获取构造函数。
困扰我的是各种类型的绑定都有其差异。我必须处理访问 LinkedKeyBinding
、UntargettedBinding
等。
有没有更简单的方法来获取给定模块列表的所有 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
我有一个 class Property<T>
,我想在我的应用程序启动后绑定它。 Property<T>
表示类型 T
的 属性,其值可以在运行时修改。
我有 classes 可以这样注入:
public class MyClass {
public MyClass(@Named("someName") Property<String> property) {
...
}
}
我可以绑定这些实例,但只有在我的应用程序启动后我才需要知道所有 Named
注释值才能这样做。
我已经开始查看 Elements
并访问所有 Element
实例以查找其所有 Binding
。一旦我有了 Bindings
,我就可以使用 InjectionPoint.forConstructor()
来获取构造函数。
困扰我的是各种类型的绑定都有其差异。我必须处理访问 LinkedKeyBinding
、UntargettedBinding
等。
有没有更简单的方法来获取给定模块列表的所有 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