如何让工厂与 Guice 协同工作?
How to make factories work well with Guice?
在我的项目中,我处处使用依赖注入,并且有两种情况使用了ad hoc factories。首先,当我想精确控制一个实例何时被创建时,我注入了一个工厂而不是一个实例:
// WidgetA must be created before WidgetB, because of the side-effects
// on the container.
WidgetAFactory.make(container);
WidgetBFactory.make(container);
另一种情况是构造函数混合使用可注入值和运行时值。而不是使用:
@Inject
WidgetC(
Container,
@WidgetCFont Font,
@WidgetCColor Color,
@Named("flag") String flag) {
...
}
我使用:
@Inject
WidgetCFactory(
@WidgetCFont Font font,
@WidgetCColor Color color,
@Named("flag") String flag) {
...
}
WidgetCFactory.make(Container container) {
return new WidgetC(container, font, color, flag);
}
但是我在使用工厂时遇到两个限制:
在我的第一个例子中,我还需要 WidgetA 成为其他 @Injected 构造函数需要的 @Singleton。到目前为止,我的解决方案是把我在调用工厂时创建的实例存储起来,@Provides给其他人使用。有没有办法让这个单例的控制权回到 guice 而不必自己维护该实例?
在我的第二个示例中,管理注入的依赖项是一团糟:WidgetCFactory 必须使用一长串注入值调用 WidgetC 构造函数,必须针对依赖项中的每次更改更新该列表,没有注释检查。有没有办法为 Guice 提供运行时参数,并让它处理其他依赖项?
感觉对于这两种情况,我都可以使用将被赋予运行时值的子注入器,并让 Guice 成为工厂:
public static class WidgetCFactory {
private final Injector injector;
@Inject
public WidgetCFactory(Injector injector) {
this.injector = injector;
}
public WidgetC make(Container container) {
Injector childInjector = injector.createChildInjector(new AbstractModule() {
@Override protected void configure() {
bind(Container.class).toInstance(container);
}
});
return childInjector.getInstance(WidgetC.class);
}
}
但我没有发现很多人这样做的案例。是因为它太重了,还是超出了依赖注入的良好实践?什么是更好的方法?
混合注入值和运行时值意味着您应该查看 "assisted injection",它允许您声明某些注入值由调用站点在运行时提供,并生成一个工厂,该工厂将仅公开那些作为一个参数。从 https://github.com/google/guice/wiki/AssistedInject 开始,您需要为要以这种方式处理的每种类型安装一个模块,例如
// this goes in your existing Module.configure()
install(new FactoryModuleBuilder()
// you can add more than one type here in this way
.implement(WidgetC.class, WidgetC.class)
.build(WidgetFactory.class));
//...
public interface WidgetFactory {
// you can add more than one method here
WidgetC createWidgetC(Container container);
}
@AssistedInject
WidgetC(
@Assisted Container,
@WidgetCFont Font,
@WidgetCColor Color,
@Named("flag") String flag) {
...
}
请特别注意对 WidgetC
构造函数的更改,构造函数上的不同注释(因为通过正常注入构造实际上并不安全)和 Container
参数(这将由工厂提供,而不是 IoC 容器。
要使 WidgetA
成为单例,您可以使用 @Singleton
修饰类型,或者在 configure()
方法中绑定它:
bind(WidgetA.class).in(Singleton.class);
正如所写,它会被懒惰地创建——它只会在第一次被请求后才存在,但之后每次被请求时,它都是同一个实例,不会从头开始创建。
回应 , Assisted Injection is the way to go, and Guice has offered Assisted Injection (through a separate dependency/JAR) since 2.0. You can read more about Guice's implementation on the Guice wiki AssistedInject page,但除了 Colin 写的内容,我没有任何示例。
您可以考虑的另一种选择是 AutoFactory,它会为您生成代码工厂实现。 (它是 Google Auto 的一部分,Auto 是 Java 的一套代码生成器,可以创建注释实现、服务、不可变值对象和工厂。)它是 Dagger 的事实标准,但适用于任何 JSR -330 框架,包括 Guice。
关于您的问题 #1,我将与 Colin 不同,您正在寻找的东西本质上有些危险:如果 @Singleton
个对象在您的应用程序的生命周期内存在,但您的 WidgetA Factory接受一个 Container,那么你的 WidgetA 有可能在你的 Container 准备好之前存在,或者它在你的 Container 被销毁之后存在。
如果你的WidgetA Container也是@Singleton
,那么你可以创建没有Factory的WidgetA,一切顺利:你可以跳过Factory,绑定Container,正常绑定WidgetA,注入一个Provider<WidgetA>
(无需额外配置即可使用)延迟 WidgetA 的创建,直到您准备好。
如果您的真正要求是 WidgetA 与 Container 存在的时间一样长,但 WidgetA/B/C 到那时都使用相同的 Container 和 WidgetA,您可以考虑 a child injector 其中你绑定你的容器和小部件。这样每个 Container 都有自己的 WidgetA,WidgetA 的每次注入在那个容器内都是一致的,当你得到一个新的 Container 时你将处理掉 WidgetA。当然,如果您的 Container 在您的 Injector 工作后才开始可用,并且在那之后是一致的,您可以使用该子注入器作为您的主注入器,然后让 WidgetA 工作。
如果您的 WidgetA 依赖的 Container 启动时不可用,请注意:这可能是一个 "scope-widening injection",因为您的 Container 将在 WidgetA 中作为 @Singleton
存在,即使否则它将被垃圾收集。这充其量可能是内存泄漏,并且可能会在您的应用程序中存在多个容器时导致奇怪的错误。您可以像一直使用的那样使用有状态模块,但无论如何都要非常小心。
在我的项目中,我处处使用依赖注入,并且有两种情况使用了ad hoc factories。首先,当我想精确控制一个实例何时被创建时,我注入了一个工厂而不是一个实例:
// WidgetA must be created before WidgetB, because of the side-effects
// on the container.
WidgetAFactory.make(container);
WidgetBFactory.make(container);
另一种情况是构造函数混合使用可注入值和运行时值。而不是使用:
@Inject
WidgetC(
Container,
@WidgetCFont Font,
@WidgetCColor Color,
@Named("flag") String flag) {
...
}
我使用:
@Inject
WidgetCFactory(
@WidgetCFont Font font,
@WidgetCColor Color color,
@Named("flag") String flag) {
...
}
WidgetCFactory.make(Container container) {
return new WidgetC(container, font, color, flag);
}
但是我在使用工厂时遇到两个限制:
在我的第一个例子中,我还需要 WidgetA 成为其他 @Injected 构造函数需要的 @Singleton。到目前为止,我的解决方案是把我在调用工厂时创建的实例存储起来,@Provides给其他人使用。有没有办法让这个单例的控制权回到 guice 而不必自己维护该实例?
在我的第二个示例中,管理注入的依赖项是一团糟:WidgetCFactory 必须使用一长串注入值调用 WidgetC 构造函数,必须针对依赖项中的每次更改更新该列表,没有注释检查。有没有办法为 Guice 提供运行时参数,并让它处理其他依赖项?
感觉对于这两种情况,我都可以使用将被赋予运行时值的子注入器,并让 Guice 成为工厂:
public static class WidgetCFactory {
private final Injector injector;
@Inject
public WidgetCFactory(Injector injector) {
this.injector = injector;
}
public WidgetC make(Container container) {
Injector childInjector = injector.createChildInjector(new AbstractModule() {
@Override protected void configure() {
bind(Container.class).toInstance(container);
}
});
return childInjector.getInstance(WidgetC.class);
}
}
但我没有发现很多人这样做的案例。是因为它太重了,还是超出了依赖注入的良好实践?什么是更好的方法?
混合注入值和运行时值意味着您应该查看 "assisted injection",它允许您声明某些注入值由调用站点在运行时提供,并生成一个工厂,该工厂将仅公开那些作为一个参数。从 https://github.com/google/guice/wiki/AssistedInject 开始,您需要为要以这种方式处理的每种类型安装一个模块,例如
// this goes in your existing Module.configure()
install(new FactoryModuleBuilder()
// you can add more than one type here in this way
.implement(WidgetC.class, WidgetC.class)
.build(WidgetFactory.class));
//...
public interface WidgetFactory {
// you can add more than one method here
WidgetC createWidgetC(Container container);
}
@AssistedInject
WidgetC(
@Assisted Container,
@WidgetCFont Font,
@WidgetCColor Color,
@Named("flag") String flag) {
...
}
请特别注意对 WidgetC
构造函数的更改,构造函数上的不同注释(因为通过正常注入构造实际上并不安全)和 Container
参数(这将由工厂提供,而不是 IoC 容器。
要使 WidgetA
成为单例,您可以使用 @Singleton
修饰类型,或者在 configure()
方法中绑定它:
bind(WidgetA.class).in(Singleton.class);
正如所写,它会被懒惰地创建——它只会在第一次被请求后才存在,但之后每次被请求时,它都是同一个实例,不会从头开始创建。
回应
您可以考虑的另一种选择是 AutoFactory,它会为您生成代码工厂实现。 (它是 Google Auto 的一部分,Auto 是 Java 的一套代码生成器,可以创建注释实现、服务、不可变值对象和工厂。)它是 Dagger 的事实标准,但适用于任何 JSR -330 框架,包括 Guice。
关于您的问题 #1,我将与 Colin 不同,您正在寻找的东西本质上有些危险:如果 @Singleton
个对象在您的应用程序的生命周期内存在,但您的 WidgetA Factory接受一个 Container,那么你的 WidgetA 有可能在你的 Container 准备好之前存在,或者它在你的 Container 被销毁之后存在。
如果你的WidgetA Container也是@Singleton
,那么你可以创建没有Factory的WidgetA,一切顺利:你可以跳过Factory,绑定Container,正常绑定WidgetA,注入一个Provider<WidgetA>
(无需额外配置即可使用)延迟 WidgetA 的创建,直到您准备好。
如果您的真正要求是 WidgetA 与 Container 存在的时间一样长,但 WidgetA/B/C 到那时都使用相同的 Container 和 WidgetA,您可以考虑 a child injector 其中你绑定你的容器和小部件。这样每个 Container 都有自己的 WidgetA,WidgetA 的每次注入在那个容器内都是一致的,当你得到一个新的 Container 时你将处理掉 WidgetA。当然,如果您的 Container 在您的 Injector 工作后才开始可用,并且在那之后是一致的,您可以使用该子注入器作为您的主注入器,然后让 WidgetA 工作。
如果您的 WidgetA 依赖的 Container 启动时不可用,请注意:这可能是一个 "scope-widening injection",因为您的 Container 将在 WidgetA 中作为 @Singleton
存在,即使否则它将被垃圾收集。这充其量可能是内存泄漏,并且可能会在您的应用程序中存在多个容器时导致奇怪的错误。您可以像一直使用的那样使用有状态模块,但无论如何都要非常小心。