Guice @Provides Methods vs Provider Classes
Asked Answered
H

2

16

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?

Hawaiian answered 28/1, 2015 at 21:36 Comment(0)
V
28

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.

Valorie answered 29/1, 2015 at 5:0 Comment(0)
B
0

There is also a difference in how classes are instantiated. For Eg :

public class GumProvider implements Provider<Gum> {

  public Gum get() {
    return new Gum();
  }
}

public class GumModule extends AbstractModule {

    protected void configure() {

        bind(Gum.class).toProvider(GumProvider.class);
        //bind(Gum.class).to(GumballMachine.class);
    }
}

public class GumballMachine {

    @Inject
    private Provider<Gum> gumProvider;

    Gum gum;


    public Gum dispense() {
        return gumProvider.get();
    }
}

public class App {

    public static void main(String[] args) {
      // TODO Auto-generated method stub
      Injector injector = Guice.createInjector(new GumModule());
      GumballMachine m = injector.getInstance(GumballMachine.class);
      System.out.println(m.dispense());
      System.out.println(m.dispense());


    }

}

This would create an Instance of Gum Per Invocation. Whereas if @Provides were used the same Instance of Gum would be passed to both the Injectors

Barri answered 21/5, 2018 at 11:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.