29

In Guice, what's the difference between:

// Inside your AbstractModule subclass:
@Override
public void configure() {
    bind(Service.class).to(ServiceImpl.class).in(Singleton.class);
}

And:

@Override
public void configure() {
    bind(Service.class).to(ServiceImpl.class);
}

@Provides @Singleton
public ServiceImpl providesService() {
    return new ServiceImpl();
}

Are they both the same? When would you use one versus the other? Thanks in advance.

IAmYourFaja
  • 55,468
  • 181
  • 466
  • 756

1 Answers1

52

They are nearly identical. The @Singleton syntax is useful for annotating @Provides methods, or annotating the class itself (though I prefer to keep my scoping annotations inside modules).

The difference lies in which key is marked Singleton, which has less to do with @Singleton versus Singleton.class (or Scopes.SINGLETON, asEagerSingleton, @Singleton class annotations, or toInstance implicit singletons) and more to do with what the default syntax makes easy. For example:

public class MyModule extends AbstractModule {
  @Override public void configure() {
    bind(A.class).to(AImpl.class).in(Singleton.class);

    bind(B.class).to(BImpl.class);
    bind(BImpl.class).in(Singleton.class);
  }

  @Provides @Singleton C provideC() { return new CImpl(); }

  @Provides @Singleton D provideD(DImpl dImpl) { return dImpl; }

  @Provides E provideE(EImpl eImpl) { return eImpl; }
  @Provides @Singleton EImpl provideEImpl() { return new EImpl(); }
}

Above we've bound interface A to class AImpl, and interface B to class BImpl, but the behavior is different:

  • Injecting A will retrieve the same AImpl instance every time.
  • Injecting AImpl will retrieve a different AImpl every time, all of which are different than A's instance.
  • Injecting B will retrieve the same BImpl instance every time.
  • Injecting BImpl will also retrieve that same BImpl instance that B injects.

As you can see, each key is different, and Guice will allow multiple implementation instances if only the interface is bound with Singleton. If you only ever inject A and B interfaces, the behavior looks identical, but if you inject both interfaces and implementations from the same Injector, you may see differing behavior.

Similar logic goes for @Provides methods:

  • Injecting C will always return the same CImpl instance.
  • Injecting CImpl will create a new CImpl every time, unless CImpl has no injectable public zero-arg constructor—then the injection will fail.
  • Injecting D will always return the same DImpl instance.
  • Injecting DImpl will return a new instance every time, and each will be different than the one returned by D.
  • Injecting E will return the same EImpl instance every time.
  • Injecting EImpl will also retrieve that same instance E injects.

This provides some flexibility. Imagine a hypothetical Cache that keeps a certain number of most-recently-retrieved objects, where you want to have @User Cache and @Product Cache both injectable. If you bind(Cache.class).in(Singleton.class), you will have one Cache shared between the objects (and any bare Cache injections), whereas if you bind(Cache.class).annotatedWith(User.class).to(Cache.class).in(Singleton.class) then the annotated key is kept in singleton scope and each object type will have its own cache.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • I have a situation where they seem to behave differently. Using the bind statement did not restrict to just 1 instance. However, using @Singleton it worked. – EFreak Mar 08 '16 at 02:06
  • @EFreak: Updated answer. It's not exactly about the difference in syntax, but with a fresh look at the question, there's some subtlety there. – Jeff Bowman Mar 08 '16 at 05:08
  • Thanks a ton! This is exactly the problem I was facing yesterday. Although, I have an edit suggestion. Will update the answer. – EFreak Mar 08 '16 at 16:23
  • I sort of think I understand this. In my case I have a concrete class LoggingConfiguration bound like so: bind(LoggingConfiguration.class).in(Singleton.class); injector = Guice.createInjector(bindingModule); return injector.getInstance(ProductsLogging.class).getLogger(clazz); Will there only be one throughout the vm? – ggb667 Mar 17 '23 at 16:56
  • @ggb667 Assuming ProductsLogging injects LoggingConfiguration, then I would guess (from what little you've told me) that every injection of LoggingConfiguration would be the same instance including in ProductsLogging. I couldn't tell you about the logger returned by `getLogger`, because that depends on the implementation of ProductsLogging. If you want a deeper analysis with more details, I think you might be better served by asking a separate question, and please make sure to include your code about how you're injecting LoggingConfiguration and which classes you want only one instance of. – Jeff Bowman Mar 17 '23 at 17:46