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.
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:
A
will retrieve the same AImpl
instance every time.AImpl
will retrieve a different AImpl
every time, all of which are different than A
's instance.B
will retrieve the same BImpl
instance every time.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:
C
will always return the same CImpl
instance.CImpl
will create a new CImpl
every time, unless CImpl
has no injectable public zero-arg constructor—then the injection will fail.D
will always return the same DImpl
instance.DImpl
will return a new instance every time, and each will be different than the one returned by D
.E
will return the same EImpl
instance every time.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.