Guice @Provides Methods vs Provider Classes

Eliezer picture Eliezer · Jan 28, 2015 · Viewed 31.2k times · Source

I'm working on a fairly large project that has a lot of injections. We're currently using a class that implements Provider for each injection that needs one, and they mostly have one line get methods.

It's starting to get annoying to create a new class every time I need a new provider. Is there any benefit to using provider classes over @Provides methods in my Module or vice-a-versa?

Answer

Jeff Bowman picture Jeff Bowman · Jan 29, 2015

As far as I know, they're exactly equivalent for most simple cases.

/**
 * Class-style provider.
 * In module: bind(Foo.class).annotatedWith(Quux.class).toProvider(MyProvider.class);
 */
class MyProvider implements Provider<Foo> {
  @Inject Dep dep;  // All sorts of injection work, including constructor injection.

  @Override public Foo get() {
    return dep.provisionFoo("bar", "baz");
  }
}

/**
 * Method-style provider. configure() can be empty, but doesn't have to be.
 */
class MyModule extends AbstractModule {
  /** Name doesn't matter. Dep is injected automatically. */
  @Provides @Quux public Foo createFoo(Dep dep) {
    return dep.provisionFoo("bar", "baz");
  }

  @Override public void configure() { /* nothing needed in here */ }
}

In either style, Guice lets you inject Foo and Provider<Foo>, even if the key is bound to a class or instance. Guice automatically calls get if getting an instance directly and creates an implicit Provider<Foo> if one doesn't exist. Binding annotations work in both styles.

The main advantage of @Provides is compactness, especially in comparison to anonymous inner Provider implementations. Note, however, that there might be a few cases where you'd want to favor Provider classes:

  • You can create your own long-lived Provider instances, possibly with constructor parameters, and bind keys to those instances instead of to class literals.

    bind(Foo.class).toProvider(new FooProvisioner("bar", "baz"));
    
  • If you're using a framework compatible with JSR 330 (javax.inject), you can easily bind to javax.inject.Provider classes or instances. com.google.inject.Provider extends that interface.

    bind(Foo.class).toProvider(SomeProviderThatDoesntKnowAboutGuice.class);
    
  • Your Provider may be complex enough to factor into its own class. Depending on how you've structured your tests, it may be easier to test your Provider this way.

  • Providers can extend abstract classes. It may not be easy or intuitive to do this with @Provides methods.

  • You can bind several keys to the same Provider directly. Each @Provides method produces exactly one binding, though you could bind other keys to the key (@Quux Foo here) and let Guice do a second lookup.

  • Providers are easy to decorate or wrap, if you wanted to (for instance) cache or memoize instances without using Guice scopes or bindings.

    bind(Foo.class).toProvider(new Cache(new FooProvisioner("bar", "baz")));
    

IMPORTANT: Though this is a good strategy for classes that Guice can't create, bear in mind that Guice can automatically create and inject a Provider<T> for any T that you bind in any way, including to a class name, key, or instance. No need to create an explicit provider unless there's actual logic of your own involved.