Guice提供商是否应该使用@Singleton注释昂贵的成员实例?

时间:2013-12-09 14:55:27

标签: java dependency-injection singleton guice

Guice Providers 是否应注明@Singleton?我的理由:如果Provider正在向其他Singleton类提供一个对象,并且该对象本身的创建成本相对较高,那么使用构造其@Inject中昂贵对象的Singleton Provider是不合理的。构造函数,将其存储为成员,并在getter中返回已保存的全局变量?像这样:

@Singleton
public class MyProvider extends Provider<ExpensiveObject> {
    private ExpensiveObject obj;

    @Inject
    public MyProvider() {
        /* Create the expensive object here, set it to this.obj */
    }

    @Override
    public ExpensiveObject get() {
        return obj;
    }
}

<小时/>

更新

让我在这里澄清一点。这不是我应该使用@Singleton还是.in(Singleton.class)。这必须通过创建对象的“缓存”来完成更多工作。

假设对象创建需要完成多个RPC,例如反序列化JSON或发出HTTP请求。这可能需要一段时间。如果我要使用这个Provider多次注入类,那么仅创建一次这样的对象是不是有意义呢?

另请注意,我必须能够使用提供商,因为我需要能够注入提供商。

3 个答案:

答案 0 :(得分:21)

如果您的问题是您是否应该创建作用域提供程序绑定,或者您是否应该手动缓存提供程序中的实例,那么实际上,不要试图比Guice更聪明:)你真的不想做任何事情而不仅仅是在get()方法中创建昂贵的对象。简单的测试用例:

public class MyProvider implements Provider<String> {
    public String get() {
        System.out.println("Called MyProvider.get()");
        return "abcd";
    }
}

public class MyModule extends AbstractModule {
    protected void configure() {
        bind(String.class).toProvider(MyProvider.class).in(Singleton.class);
    }
}

Injector injector = Guice.createInjector(new MyModule());
String abcd1 = injector.getInstance(String.class);  // Prints "Called MyProvider.get()
String abcd2 = injector.getInstance(String.class);  // Prints nothing!
// Or, if you want, comment out above two lines and try the following:
Provider<String> abcdProvider = injector.getProvider(String.class);
abcdProvider.get();  // Prints "Called MyProvider.get()"
abcdProvider.get();  // Prints nothing

你知道,因为消息只打印了一次,MyProvider.get()方法也只被调用一次,正是因为String被绑定在单一范围内。

这里要理解的关键概念是提供者和绑定不是单独的实体。每个绑定都有一个关联的提供程序(当您使用to()创建普通绑定时,会为您创建一个隐式提供程序)。这可以通过getProvider()方法签名轻松地观察到 - 它接受Class<T>Key<T>表示您想要获得的实际课程,而不是您已绑定的提供者。创建与特定提供程序的绑定时,如果不配置此提供程序,则需要配置绑定。即使您使用显式提供程序,Guice也足够聪明,可以将范围考虑在内,因此您不需要重新发明轮子并推出自己的单例。

如果你的问题是关于@Singleton注释的使用(而不是bind() DSL),那么我不知道它在提供者类上的存在是否会产生任何影响,但鉴于你应该无论如何使用bind().toProvider()绑定到此提供程序,我认为这不重要。只需使用in()方法,它肯定会有效。

答案 1 :(得分:8)

请注意,使用.in(Singleton.class)并在提供程序类上使用@Singleton注释将提供程序绑定到Singleton范围之间存在重大差异。

  1. 在第一种情况下,get()方法只调用一次,结果将存储在Singleton范围内。
  2. 在第二种情况下,提供者实例只创建一次,但为应用程序中的每个注入点调用get()方法。
    如果您使用此方法,手动缓存昂贵的对象将是明智的。但这样做真的没有意义。只需使用第一种方法即可。
  3. 您甚至可以将这两种策略结合起来,例如:通过使用@Singleton注释提供程序,并使用.in(RequestScoped.class)将提供程序结果绑定到请求范围。如果没有注释,您的提供程序将针对每个请求进行实例化,如果它存储有状态数据可能会很重要。

    这只是为了澄清,因为有些读者可能会偶然发现您的问题,并且可能认为这两种方法在语义上是平等的。

答案 2 :(得分:1)

是的,原则上,但在这种情况下,你可以完全取消提供者,只需创建ExpensiveObject作为一个渴望的单身人士。它只会在创建注入器时实例化一次,并且该实例将在需要的任何地方注入。