Atom Feed SITE FEED   ADD TO GOOGLE READER

What's a Hierarchical Injector?

Our application has two implementations for one interface. EnergySource is implemented by both Plutonium and LightningBolt:
class DeLorean {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;
@Inject EnergySource energySource;
}

interface FluxCapacitor {
boolean isFluxing();
}

@Singleton
class RealFluxCapacitor implements FluxCapacitor {
@Inject TimeCircuits timeCircuits;
boolean isFluxing;

public boolean isFluxing() {
return isFluxing;
}
}

class TimeCircuits {
Date whereYouveBeen;
Date whereYouAre;
Date whereYourGoing;
}

interface EnergySource {
void generateOnePointTwentyOneGigawatts();
}

class Plutonium implements EnergySource { ... }

class LightningBolt implements EnergySource { ... }

And to allow for sequels, we assume other implementations of EnergySource are possible. We'd like to create an Injector immediately and create a Plutonium-powered DeLorean. Shortly thereafter, we'd like to re-use that same Injector, but with a lightning bolt for energy.


Option one: Factory classes


We can solve this problem by introducing a DeLorean.Factory interface that accepts an EnergySource as its only parameter:
class DeLorean {
private final TimeCircuits timeCircuits;
private final FluxCapacitor fluxCapacitor;
private final EnergySource energySource;

DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}

static class Factory {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;

DeLorean create(EnergySource energySource) {
return new DeLorean(timeCircuits, fluxCapacitor, energySource);
}
}
}

This works for our specific problem, but in general it's quite awkward:
  • It requires a gross amount of boilerplate code.
  • It discourages refactoring of the DeLorean class.
  • It increases the complexity of getting an EnergySource.
  • It doesn't work unless EnergySource is a direct dependency of the DeLorean class. Otherwise you need to create lots of little factories that cascade.
  • And EnergySource is no longer in-the-club—it doesn't participate in Guice's injection, AOP, scoping, etc.


Option two: AssistedInject


AssistedInject is a Guice extension that's intended to reduce the boilerplate of option one. Instead of a factory class, we write a factory interface plus annotations:
class DeLorean {
TimeCircuits timeCircuits;
FluxCapacitor fluxCapacitor;
EnergySource energySource;

@AssistedInject
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
@Assisted EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}

interface Factory {
DeLorean create(EnergySource energySource);
}
}

This fixes some problems. But the core issue still remains: getting an instance of EnergySource is difficult. Unlike regular Guice (@Inject is the new new), you need to change all callers if you add a dependency on EnergySource.

Option three: Hierarchical Injectors


The premise is simple. @Inject anything, even stuff you don't know at injector-creation time. So our DeLorean class would look exactly as it would if EnergySource was constant:
class DeLorean {
TimeCircuits timeCircuits;
FluxCapacitor fluxCapacitor;
EnergySource energySource;

@Inject
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}
}

To use it, we start with an Injector that had bindings for everything except for EnergySource. Next, we create a second injector that extends the first, and binds either Plutonium or LightningBolt. This second injector fills in its missing binding.

The injectors share singletons, so we don't have to worry about having multiple TimeCircuits. Static analysis is applied to both injectors as a whole, where complete information is known. And all objects are in-the-club and get Guice value-adds like injection, scoping and AOP.

This is the solution to the mystical Robot Legs problem, wherein we have a RobotLeg class, that needs be injected with either a LeftFoot or a RightFoot, depending on where the leg will ultimately be used.

Criticism of Hierarchical Injectors


They suggest competing bindings. One parent injector could have relations with multiple child injectors. In our example, the parent injector binds DeLorean and TimeCircuits, and each child binds a different EnergySource.

They require abstract Injectors. The parent injector in our example wouldn't be able to create an instance of DeLorean, since it doesn't have all of the prerequisite bindings. This is just weird.

They're complex. Guice was born out of making code simpler. Does the conceptual weight of hierarchical injectors justify their inclusion?

Going forward


Today's Guice includes a simplified implementation of hierarchical injectors written by Dan Halem. It doesn't cover the interesting (but complex) case where the parent injector cannot fulfill all of its bindings. I'm studying the use cases, trying to come up with a balance between ease-of-use and power.

For example, one idea is to require users to explicitly call-out bindings that child injectors will provide:
  public void configure() {
bind(EnergySource.class).throughChildInjector();
}

I'd also like to do something similar to AssistedInject's factory interfaces. This way the second injector would be created, used and discarded transparently, so the user never needs to see it. From the user's perspective, this would just be like AssistedInject, but the assisted parameters could be injected anywhere.

If you have suggested use-cases or ideas, I'd love to hear 'em.
There is another drawback with this solution: The object graph you want to retrieve now depends on the injector. You have two functional injectors and there is no intrinsic way to get on of the graphs injected at the moment.

In terms of the Robot Legs problem: There is no way to inject both legs into the body of the robot.
Update: everything but abstract bindings is implemented...