Interplay between parent injectors and JIT bindings

44 views
Skip to first unread message

limpb...@gmail.com

unread,
Oct 7, 2008, 6:32:42 PM10/7/08
to google-guice-dev
I'm doing test-first development, trying to figure out
how hierarchical injectors will work.

The current (dhalem) implementation of hierarchical
injectors does something reasonable with just-in-time
bindings -- each injector gets its own map. And they're
allowed to overlap with their parents.

This might be fine for unscoped bindings but it'll bring
a world of hurt for scoped ones. For example, what
should happen for this test?

public void testJitBindings() {
Injector parent = Guice.createInjector();
Injector child = Guice.createInjector(parent, new AbstractModule()
{
protected void configure() {
bind(A.class).toInstance(new A("from-module"));
}
});

B fromParent = parent.getInstance(B.class);
assertEquals("just-in-time", fromParent.a.value);

B fromChild = child.getInstance(B.class);
assertEquals("from-module", fromChild.a.value);
}

static class B {
private final A a;

@Inject B(A a) {
this.a = a;
}
}

static class A {
private final String value;

@Inject
public A() {
this("just-in-time");
}

A(String value) {
this.value = value;
}
}


My instinct is to be as restrictive as possible...

1. Forbid any binding from occurring twice in an
injector hierarchy. Parent injectors can't have JIT
bindings that their children have explicit bindings
for and vice versa.

2. Child injectors don't have their own JIT bindings.
All JIT bindings live in the root injector.

3. Child injectors can have private singletons, but
they must be bound explicitly.

For #1 to work, the parent would maintain a set[1] of
blacklisted bindings. These are the explicit bindings
of the child injectors, and the parent promises to
never create JIT bindings for 'em.

Got a simpler/better approach? Please let me know.

Cheers,
Jesse

[1] probably something involving weak references,
so the parent injector can survive its children

limpb...@gmail.com

unread,
Oct 13, 2008, 2:41:40 PM10/13/08
to google-guice-dev
I discussed this with Bob and I'm getting
closer to formalizing the rules for parent
injectors:

1. Bindings, scopes, interceptors, constants
and type converters are all inherited.

2. When building JIT bindings, Guice will create
the binding at the highest-ancestor possible.
Both parent and children maintain their own
sets of JIT bindings.

3. No Key may be bound by both an injector
and one of its ancestors. This includes JIT
bindings. The lone exception is the key for
Injector.class. If you inject an Injector, you'll
get the injector that contains your binding.


New APIs:

I'm going to introduce two new core APIs to make
this all work together.

Injector.createChildInjector(). I prefer this over the
current Guice.createInjector(Injector, Stage, Module)
overload, which newbies are presented with when
calling Guice.createInjector(). It doesn't make
sense to use the same method for both simple
and hierarchical injectors.

Injector.getParent(). This simplifies the contract for
methods like Injector.getBindings(). Otherwise I'd need
to somehow return the union of the bindings for the
injector and its ancestors. It also makes the answer to
Injector.getBindings().get(Key.get(Injector.class)
unambiguous.


Interesting Consequences

One surprising consequence of these rules is that you'll
often get the root injector when you might not otherwise
expect it. For example, this test will fail:

interface A {
Injector getInjector();
}

class RealA implements A {
@Inject Injector myInjector;
public Injector getInjector() {
return myInjector;
}
}

public void test() {
Injector parent = Guice.createInjector();
Injector child = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(A.class).to(RealA.class);
}
});

assertSame(child, child.getInstance(A.class).getInjector());
}

The reason is that although the binding for A.class is in the
child injector (a linked binding), the binding for RealA.class
(a constructor binding) is in the parent injector. I do not think
this will be an issue, but it's something we should be mindful
of.

Cheers,
Jesse

Bob Lee

unread,
Oct 13, 2008, 2:45:33 PM10/13/08
to google-g...@googlegroups.com
Jesse, maybe we should inject the Injector from the context, i.e. the injector being used to create the current object graph? Then, we'd need only one binding to Injector in the root Injector.

Bob

Sam Berlin

unread,
Oct 13, 2008, 2:53:51 PM10/13/08
to google-g...@googlegroups.com
While hierarchical injectors are under better construction, I wrote a
temporary utility method (based on lots of emails here) --

/** Returns a module that returns all providers from the parent module. */
public static Module providersFrom(final Injector parent) {
return new Module() {
@SuppressWarnings("unchecked")
public void configure(Binder binder) {
// These types cannot be rebound.
Key loggerKey = Key.get(Logger.class);
Key injectorKey = Key.get(Injector.class);
Key stageKey = Key.get(Stage.class);

for(Map.Entry<Key<?>, Binding<?>> entry :
parent.getBindings().entrySet()) {
Key key = entry.getKey();
Binding binding = entry.getValue();
if(!key.equals(loggerKey) &&
!key.equals(injectorKey) && !key.equals(stageKey)) {
binder.bind(key).toProvider(binding.getProvider());
}
}

}
};
}

The purpose being to workaround issues with hierarchical injection
while still allowing child injectors. The use is:

Injector child = Guice.createInjector(new ChildModule(),
providersFrom(parentInjector));

IMO, a proper hierarchical injector should provide a 'better' way of
doing this. That is:

Injector child = parentInjector.createChildInjector(new ChildModule());

and have it enable all the properties the workaround enables (while
still allowing nifty things that only a true parent/child relationship
can offer).

Sam

limpb...@gmail.com

unread,
Oct 13, 2008, 4:13:08 PM10/13/08
to google-guice-dev
On Oct 13, 11:45 am, "Bob Lee" <crazy...@crazybob.org> wrote:
> Jesse, maybe we should inject the Injector from the context, i.e. the
> injector being used to create the current object graph? Then, we'd need only
> one binding to Injector in the root Injector.

We need to inject the Injector that owns the
binding, not the one that's creating the graph.
Consider a hypothetical app that has child
injectors for the persistence and web layers:

class PersistenceLayer {
@Inject Injector injector;
protected <T> Persister<T> getPersister(Class<T> c) {
return injector.getInstance(c);
}
}

class WebLayer {
@Inject Injector injector;
void serviceRequest(HttpRequest request) {
Class<? implements Handler> handler =
mapRequestToHandler(request);
injector.getInstance(handler);
handler.handle(request);
}
}

If the application binding depends on both of
these child injectors, they should be the
appropriate ones.

class Application {
@Inject PersistenceLayer persistenceLayer;
@Inject WebLayer webLayer;
}

This is because our model assumes that child
injectors can promote bindings to the parent via
hierarchical modules.

Cheers,
Jesse

tzwoenn

unread,
Oct 15, 2008, 9:16:12 AM10/15/08
to google-guice-dev
I had a similar idea as Jesse when solving the problem of having
multiple EntityManagers bound to one Injector by using annotated
bindings. The requirements are
1. having multiple object graphs with slightly different bindings
(e.g. the EntityManagerFactory is different for each PerstenceUnit),
but
2. reuse the classes that have @Inject dependencies
3. bound them all to one Injector with different binding annotations.

This requirements were discussed on this mailing list several times
often using the term "robot-leg-problem". My idea was to create a
fully usable (Sub-)Injector for each PersistenceUnit by binding all
necessary types without any annotations. This ensures I can reuse my
types, providers, etc. without using any fancy logic to be able to
Inject annotated bindings (this solves point 2). I have introduced the
term SubInjector, because after creating all SubInjectors I create a
root Injector that is visible to the application by iterating over all
SubInjector's bindings and bind them to the root Injector using one
binding annotation for each SubInjector (solves point 3). This looks
like:

class SubInjector {
interface AnnotatedSubModule {
Module annotatedWith(Class<? extends Annotation>);
Module annotatedWith(Annotation);
}

static AnnotatedSubModule bind(Module...) {
// create injector from modules and
// return a module with annotated bindings
// to this injector.getBindings().getProvider()
}
}

with the concrete usage for binding different PerstenceUnits:

Injector applicationInjector = Guice.createInjector(
SubInjector.bind(JpaModule.createModuleForUnitName("crm"))
.annotatedWith(CustomerRelationManagement.class),
SubInjector.bind(JpaModule.createModuleForUnitName("business"))
.annotatedWith(BusinessTasks.class)
)

class JpaModule {
static Module createModuleForUnitName(final String unitName) {
return new AbstractModule() {
bind(EntityManagerFactory.class)
.toInstance(javax.persistence.Persistence
.createEntityManagerFactory(unitName));
bind(EntityManager.class).toProvider(new
Provider<EntityManager>() {
@Inject EntityManagerFactory factory;

public EntityManager get() {
return factory.createEntityManager();
}
}
});
}
}

class Application {
@Inject @CustomerRelationManagement EntityManager crmEntityManager;
@Inject @BusinessTasks EntityManager taskEntityManager;
}


Back to discussion: My current wish/problem is, I am not able to
reference bindings of the root Injector from the SubInjectors. I would
love to see the each Subinjector created as an child Injector of the
parent/root Injector, but the problem is that the parent Injector
actually has to be fully initialized before you can use it to create
child Injectors. Because I am creating modules for each child/sub
injector which are used to configure the parent/root injector
afterwards, I am not able to also use the parent injector as a place
of common bindings, that are available for each child injector (sounds
a bit like the chicken-or-egg question to me).

I know a can deal with this problem by simply using multiple child
injectors as it is stated by the current implementations of
"hierarchical injectors", but I do not like the idea of handling
multiple injectors in the application like Jesse said before.

Best regards,
Sven
Reply all
Reply to author
Forward
0 new messages