Should Guice Providers with expensive member instances as be annotated with @Singleton?

ecbrodie picture ecbrodie · Dec 9, 2013 · Viewed 10.8k times · Source

Should Guice Providers be annotated with @Singleton? My justification: if the Provider is providing an object to other Singleton classes and the object itself is relatively expensive to create, then wouldn't it make sense to use a Singleton Provider that constructs the expensive object in its @Inject-marked constructor, store it as a member and just return that already-saved global variable in the getter? Something like this:

@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;
    }
}


Update

Let me clarify a little bit more here. This is not about whether I should be using @Singleton or .in(Singleton.class). This has to do more with the "caching" of the created object.

Let's say that object creation required multiple RPCs to complete, such as deserializing JSON or making HTTP requests. This could take quite some time. If I am going to use this Provider to inject into classes multiple times, then doesn't it make sense to only create such an object once?

Also note that I must be able to use a Provider because I need to be able to inject into the Provider.

Answer

Vladimir Matveev picture Vladimir Matveev · Dec 9, 2013

If your question is whether you should create scoped provider bindings, or if you should cache instances in your providers manually, then really, do not try to be smarter than Guice :) You really do not want to do anything more than just create your expensive object in get() method. Simple test case:

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

You see, because the message was printed only once, MyProvider.get() method was called only once too, exactly because String is bound in singleton scope.

The key concept to understand here is that providers and bindings are not separate entities. With every binding there is an associated provider (when you create plain bindings with to(), an implicit provider is created for you). This can easily be observed from getProvider() method signature - it accepts Class<T> or Key<T> for actual class you want to get, not for the provider you have bound. When you create a binding to specific provider, you do not configure this provider, you configure the binding. Guice is smart enough to take scopes into account even if you use explicit providers, so you just do not need to reinvent the wheel and roll out your own singleton.

If your question is specifically about usage of @Singleton annotation (as opposed to bind() DSL), then I don't know whether its presence on provider class gives any effect, but given that you should use bind().toProvider() to bind to this provider anyway, I don't think that it really matters. Just use in() method, it will certainly work.