【问题标题】:Guice: wiring a bean with complex creational patternGuice:用复杂的创建模式连接一个 bean
【发布时间】:2013-02-05 20:34:02
【问题描述】:

我正在使用 Guava 创建一个 EventBus 用于发布-订阅消息服务。我也是第一次尝试使用 Guice。我已经阅读了 Guice 教程,并且一直在玩 AbstractModuleBinder 类。但是当我离开教程并尝试真正为我的项目工作的那一刻,我就窒息了。

我的项目有一个 EventMonitor,它将有一个 Guice 注入的 Guava 实例 EventBus

public class EventMonitor {
    @Inject
    private EventBus guavaEventBus;

    // ...
}

在我的应用程序的 Guice/DI/Bootstrapping 代码中,我定义了一个AbstractModule concretion:

public class MyAppModule extends AbstractModule {
    @Override
    public void configure() {
        // Here is where I want to wire together the EventBus to give
        // to the EventMonitor.
    }
}

最终,我想要一个 EventBus,它通常会像这样构造(在非 Guice 代码中):

ThreadFactory factory = ThreadManager.currentRequestThreadFactory();
Executor executor = Executors.newCachedThreadPool(factory)
EventBus eventBus = new AsyncEventBus(executor);

因为ThreadManagerExecutors 上的两个(看似不可注入的)静态方法,我感到窒息,因为我的参考是EventBus,但实际对象是AsynEventBus;因此我不确定如何绑定它:

// Doesn't work because how does Guice know I'm referencing an AsyncEventBus?!?
bind(EventBus.class).toInstance(executor);

// Doesn't work because now I've lost the ability to pass the
// AsyncEventBus an 'executor' and no-arg ctor is used!
bind(EventBus.class).to(AsyncEventBus.class);

所以我问:考虑到我想构建我的EventBus 的方式,一位身经百战的 Guice 退伍军人将如何在这里连接(使用 ThreadFactoryExecutorEventBus)这样在EventMonitor 中,完全配置的EventBus 被正确注入了吗?我想一旦我看到这个更“复杂”的例子,我就会开始透过树木看到森林。提前致谢。

【问题讨论】:

    标签: java dependency-injection guice


    【解决方案1】:

    一个身经百战的古斯老兵怎么会在这里发消息

    两个词:提供者方法。

    public class MyAppModule extends AbstractModule {
        @Override
        public void configure() {
            bind(EventBus.class).to(AsyncEventBus.class);
        }
    
        @Provides @Singleton
        ThreadFactory providesThreadFactory() {
            return ThreadManager.currentRequestThreadFactory();
        }
    
        @Provides @Singleton
        Executor providesExecutor(ThreadFactory factory) {
            return Executors.newCachedThreadPool(factory)
        }
    
        @Provides @Singleton
        AsyncEventBus providesAsyncEventBus(Executor executor) {
            return new AsyncEventBus(executor);
        }
    }
    

    命名@Provides 方法以“provides”开头的约定并不是 guice 需要的,而是您真正想要做的事情,尤其是在大型代码库上。能够在代码体中搜索“provides”并找到提供特定对象的方法。

    回答cmets问题的几点注意事项:

    • Guice 检查您为@Provides 方法安装的每个Module 实例,并安装它们,就好像您将带有.toProvider 的方法的返回类型绑定到匿名Provider 实例一样。所以关于它们的好处是,您不需要在 configure 方法中添加任何额外的代码就可以使用它们。

    • @Singleton 注释告诉 guice 你只想要那个东西的一个实例,所以它只会调用一次provides* 方法。默认情况下,如果您将其关闭,则在每次需要注入实例时让 guice 调用您的提供程序和/或实例化一个新对象(取决于您为该类配置的内容)。注意:有些人一发现这个就吓坏了,然后想把@Singleton到处“为了效率”——这是不恰当的反应。实际上,您确实希望谨慎使用 @Singleton,并且仅在使用多个不同实例不正确的情况下使用。

      在这种情况下,我们要非常确定周围只有一个EventBus。只要您不直接将ExecutorThreadFactory 注入任何其他类,您就可以将这些方法的注释去掉。对于ThreadFactory,这几乎肯定不会有什么不同,因为我认为ThreadManager.currentRequestThreadFactory() 每次调用时都会返回相同的实例。 Executor 会有所不同,但也许您想在其他地方使用新的 Executor 实例?

    • @Provides 方法的参数与其余配置的连接方式相同。例如,在这种情况下,我知道providesAsyncEventBus 将得到providesExecutor 返回的Executor,因为这是实例guice 将在被要求提供Executor 时注入。如果我有两种这样的方法:

      @Provides @Singleton
      Executor providesExecutor1(ThreadFactory factory) {
          return Executors.newCachedThreadPool(factory)
      }
      
      @Provides @Singleton
      Executor providesExecutor2(ThreadFactory factory) {
          return Executors.newScheduledThreadPool(10, factory)
      }
      

      那么当你尝试创建注入器时 guice 会抛出一个配置错误,因为你告诉 guice 使用两种不同的方式来获取Executor。现在,如果我有这样的东西:

      @Provides @Singleton
      ExecutorService providesExecutor1(ThreadFactory factory) {
          return Executors.newCachedThreadPool(factory)
      }
      
      @Provides @Singleton
      ScheduledExecutorService providesExecutor2(ThreadFactory factory) {
          return Executors.newScheduledThreadPool(10, factory)
      }
      

      但仍然有上述providesAsyncEventBus 方法,那么当您尝试创建注入器时,guice 会抛出错误,因为您没有告诉它如何创建providesAsyncEventBus 需要的Executor。您需要额外的一行:

      bind(Executor.class).to(ExecutorService.class);
      

      在您的 configure 方法中解决问题。

    • 至于您的register 方法,您有几个选择。我认为到目前为止最简单的方法是将EventBus 参数添加到需要注册的对象的@Inject-annotated 构造函数中,然后将evtBus.register(this) 作为构造函数的最后一行。

      其他选项包括使用静态注入(您可以阅读相关内容,但我不推荐),或使用多绑定器将Set<Object> 与适当的注释绑定,然后在您创建Injector 的同一启动代码中,遍历该集合以注册任何内容。 (第二种方法可以用一些不错的模式完成,但在您了解更多关于更简单的 guice 使用模式之前,我不会推荐它)

    【讨论】:

    • 感谢@Daniel Martin (+1) - 3 个快速跟进:@Singleton 的经验法则是什么?提供者是否应该始终提供@Singleton?如果我没有在所有这些提供商上指定它会发生什么?其次,EventBus 对象上有一个register(Object) 方法,我在其中注册了所有不同的“事件侦听器”。 我在哪里拨打这些register 电话? 我假设,如果我想要同样的,单身EventBus 每次我向 Guice 要求一个时都会返回,我必须从 @ 内部拨打这些电话987654364@,是吗?再次感谢!
    • 第三,Guice 知道自动连接提供程序方法吗?例如,providesAsyncEventBus(Executor) 需要 Executor。由于 @Providers 注释,Guice 知道使用 providesExecutor 吗?执行器是ScheduledThreadPoolExecutorThreadPoolExecutor 的子类。如果你有两个提供者方法,每个子类一个呢? providesAsyncEventBus(Executor) 如何知道要调用哪个提供程序方法来获取 Executor
    • 更新了解决这些 cmets 的答案
    • 我认为最好在Executor 的提供者方法中添加绑定注释(或者,更简单地说,在AsyncEventBus 的提供者方法中创建Executor)。您可能不希望任何注入 Executor 的人使用与事件总线相同的线程池
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 2015-01-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多