Lucas Wa
Lucas Wa

Reputation: 530

Why Guice prevents from binding to Provider?

Recently, when I played around with Google Guice I was trying to do something like this:

@Override
protected void configure() {
    ...
    bind(Provider.class).to(ViewFactory.class);
    ...
}

Where ViewFactory was:

public class ViewFactory implements Provider<SomeType> {...}

Of course, Guice didn't let me do that returing error:

1) Binding to Provider is not allowed.
{stacktrace}

What is the reason why it is not possible to bind to provider?

Upvotes: 2

Views: 7024

Answers (2)

Vladimir Matveev
Vladimir Matveev

Reputation: 127761

I guess it is because Provider interface is very special to Guice. In fact, all its internal machinery is implemented in term of providers.

Moreover, this could create ambiguities. If bindings to providers were possible:

bind(SomeClass.class).to(SomeClassImpl1.class);
bind(new TypeLiteral<Provider<SomeClass>>() {}).to(() -> new SomeClassImpl2());

then what should Guice inject here?

@Inject
OtherClass(Provider<SomeClass> someClassProvider) { ... }

Should it be a provider which returns SomeClassImpl1 (because of the first binding; remember, direct injections and provider injections are interchangeable in Guice) or should it be a provider which returns SomeClassImpl2 (because of the second binding)?

It really is redundant. Because you can inject SomeClass or Provider<SomeClass> regardless of the actual binding, you can bind the class itself to its provider:

bind(SomeClass.class).toProvider(() -> new SomeClassImpl());

// Either of the following will work
@Inject
OtherClass1(Provider<SomeClass> someClassProvider) { ... }

@Inject
OtherClass2(SomeClass someClass) { ... }

Upvotes: 7

durron597
durron597

Reputation: 32323

Provider is a special case. Guice does a lot of things behind the scenes with Provider, so they just ban binding to the Provider class entirely. One example is with scoping: your custom Provider might call new every single time, but if you create the provider in the Singleton scope, that should not happen. So Guice doesn't actually inject your provider, it injects a wrapped version. Things like that is why they ban binding to Provider.class directly. Here's a code example:

import com.google.inject.*;
import com.google.inject.name.*;

public class ProviderBindExample {
  public static class ProvModule extends AbstractModule {

    @Override
    protected void configure() {
      bind(Foo.class).toProvider(FooProvider.class);
      bind(Foo.class).annotatedWith(Names.named("singleton"))
          .toProvider(FooProvider.class)
          .in(Singleton.class);
    }
  }

  public static interface Foo { }

  public static class FooProvider implements Provider<Foo> {
    @Override
    public Foo get() {
      return new Foo() {};
    }
  }

  public static class SomeClass {
    @Inject public Provider<Foo> provider;
    @Inject @Named("singleton") public Provider<Foo> singletonProvider;
  }

  public static void main(String... args) {
    Injector inj = Guice.createInjector(new ProvModule());
    SomeClass s = inj.getInstance(SomeClass.class);
    System.out.println("Provider class = " + s.provider.getClass());
    System.out.println("Singleton provider class = " + s.singletonProvider.getClass());

    Foo first = s.provider.get();
    Foo second = s.provider.get();

    System.out.printf("regular scope: objects are %s%n", first == second ? "the same" : "different");

    first = s.singletonProvider.get();
    second = s.singletonProvider.get();

    System.out.printf("singleton scope: objects are %s%n", first == second ? "the same" : "different");
  }
}

Output:

Provider class = class com.google.inject.internal.InjectorImpl$4
Singleton provider class = class com.google.inject.internal.InjectorImpl$4
regular scope: objects are different
singleton scope: objects are the same

Upvotes: 2

Related Questions