Issue in private modules

54 views
Skip to first unread message

Aleksey Didik

unread,
May 22, 2009, 7:16:48 AM5/22/09
to google-guice
Hello all,
looks like I have found the bug into private modules binding.

Briefly: Private modules couldn't provide possibility to create 2
different trees of singletons if dependency hierachy is deeply than 2.

Guice version: 2.0

My steps:
1) Create the interface Builder and it's implementation BuilderImpl
annotated with @Singleton

interface Builder {}

@Singleton
static class BuilderImpl implements Builder {}

2) Create the interface Manager and it's implementation ManagerImpl
annotated with @Singleton which depends on Builder (constructor
injection)

interface Manager {
Builder getBuilder();
}

@Singleton
static class ManagerImpl implements Manager {
final Builder builder;

@Inject
ManagerImpl(Builder builder) {
this.builder = builder;
}

public Builder getBuilder() {
return builder;
}
}

3. Create class InfoPane which depends on Manager.

static class InfoPane {

final Manager manager;

@Inject
InfoPane(Manager manager) {
this.manager = manager;
}
}

4. Let's define 2 different instance of InfoPane in test app, every
instance will have own instance of Manager, manager instance will have
own builder instance. Will use private modules and binding with
annotations:

Injector inj = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {

install(new PrivateModule() {
protected void configure() {
bind(Builder.class).to(BuilderImpl.class);
bind(Manager.class).to(ManagerImpl.class);
bind(InfoPane.class).annotatedWith(Names.named
("first")).to(InfoPane.class);
expose(InfoPane.class).annotatedWith
(Names.named("first"));
}
});

install(new PrivateModule() {
protected void configure() {
bind(Builder.class).to(BuilderImpl.class);
bind(Manager.class).to(ManagerImpl.class);
bind(InfoPane.class).annotatedWith(Names.named
("second")).to(InfoPane.class);
expose(InfoPane.class).annotatedWith
(Names.named("second"));
}
});
}
});

5. Let's get instances of "first" InfoPane and "second" and check that
they have different instances of Manager in spite of @Singleton
annotations:

InfoPane firstPane = inj.getInstance(Key.get(InfoPane.class,
Names.named("firstPane")));
InfoPane secondPane = inj.getInstance(Key.get(InfoPane.class,
Names.named("secondPane")));
final Manager firstManager = firstPane.manager;
final Manager secondManager = secondPane.manager;
System.out.printf("Are managers the same = %s\n",
firstManager.equals(secondManager));

It's ok, both managers are different instances of ManagerImpl class.

6. Let's check that firstManager builder is not the same instance of
secondManager builder:

System.out.printf("Are builders the same = %s\n",
firstManager.getBuilder().equals(secondManager.getBuilder()));

Upps, firstManager.getBuilder() is the same as secondManager.getBuilder
(), it's one instance of BuilderImpl.

But as I can understand, they must be the different object, in spite
of @Singleton, because they binded in different private modules.

It's looks like a bug.

Full test here http://pastie.org/486289

I tested it with binding in scope (bind(Builder.class).to
(BuilderImpl.class).in(Scopes.SINGLETON)), it's work the same.
I increased dependency hierachy deep (Builder depends on Foo and
FooImpl(@Singleton)), it's work the same.






Aleksey Didik

unread,
May 22, 2009, 7:23:13 AM5/22/09
to google-guice

limpb...@gmail.com

unread,
May 22, 2009, 12:15:50 PM5/22/09
to google-guice
Short version: add the following statement to your private module and
you'll get your desired behaviour.
bind(ManagerImpl.class);
bind(BuilderImpl.class);

Longer version:
When you say: bind(Foo.class).to(FooImpl.class), you're creating an
explicit binding for Foo that links to a just-in-time binding for
FooImpl. From the Injector.createChildInjector() javadoc,
Just-in-time bindings created for child injectors will be created in
an ancestor injector whenever possible. This allows for scoped
instances to be shared between injectors. Use explicit bindings to
prevent bindings from being shared with the parent injector.
Guice prefers to share bindings whenever it has the opportunity. If
it's creating a Just-in-time binding, it'll do so in the top-level
injector wherever possible.

I admit that this is somewhat mindboggling. Of the two possible
behaviours, we went with this one because we prefer to err on the side
of singletons really being singletons.

Sam Berlin

unread,
May 22, 2009, 12:24:41 PM5/22/09
to google...@googlegroups.com
Jesse,

Is this the same reason that prevents child injectors using bind(Interface.class).to(Impl.class) from having the Impl eagerly created if the parent injector is using Stage.PRODUCTION?

Sam

Aleksey Didik

unread,
May 22, 2009, 3:38:09 PM5/22/09
to google-guice
Ok, it's clear for me, Jesse.
Thanks for so fast response.

But, as to me it's very mind-boggling...

> Just-in-time bindings created for child injectors will be created in
> an ancestor injector whenever possible.

Whenever possible? Why in my example ManagerImpl was created in child
injector (two different singletons), but BuilderImpl was created in
ancestor (one singleton for both trees)? Both is just-in-time
bindings, isn't it?

Aleksey.

limpb...@gmail.com

unread,
May 23, 2009, 4:06:27 PM5/23/09
to google-guice
On May 22, 12:38 pm, Aleksey Didik <aleksey.di...@gmail.com> wrote:
> Whenever possible? Why in my example ManagerImpl was created in child
> injector (two different singletons), but BuilderImpl was created in
> ancestor (one singleton for both trees)? Both is just-in-time
> bindings, isn't it?

The ManagerImpl cannot be created in the parent injector because its
dependencies aren't there (Builder.class is only available in the
child).
But BuilderImpl doesn't have any dependencies, and so it'll be created
in the parent injector.

For explicit bindings, where it's bound is where it'll be.
For just-in-time bindings (JIT bindings), it'll be bound at the
highest ancestor that satisfies all dependencies.

Because the JIT case is so, unpredictable, I recommended explicit
bindings for child injectors.

Stuart McCulloch

unread,
May 24, 2009, 2:43:52 AM5/24/09
to google...@googlegroups.com
2009/5/24 je...@swank.ca <limpb...@gmail.com>

perhaps this is another situation where the Grapher could help - does it handle injector hierarchies?

Aleksey Didik

unread,
May 25, 2009, 2:29:28 AM5/25/09
to google-guice
Thanks for explanations, Jesse.

Aleksey.
Reply all
Reply to author
Forward
0 new messages