Brian Slesinsky's Weblog
 
   

Sunday, 08 Apr 2007

Guice, the Automatic Puzzle Factory

Lately I've been puzzled and intrigued by a Java 5 library called Guice, which Google released as open source last month. Guice is what's known as a dependency injection framework. It's sometimes compared to Spring, but that won't help you if you haven't used dependency injection frameworks or Spring. I think of Guice as a toolkit for building graphs of interdependent objects, or to put it another way, it's an automatic puzzle factory. Here's a short introduction to how Guice works.

simple Guice tree

It sometimes happens that you want to plug a bunch of Java objects together into a tree or graph. For example, suppose you have interfaces A, B, C, and D, which are implemented by classes AImpl, BImpl, CImpl, and DImpl. (Guice doesn't require you to use interfaces, but it makes the example easier.) To build a tree without Guice, we create the nodes bottom-to-top starting with the leaf nodes:

public static A makeA() {
    B b = new BImpl(new DImpl());
    C c = new CImpl(new DImpl());
    return new AImpl(b, c);
}

Guice can create the same tree automatically. Once we set up a factory using Guice, we only need to tell it what kind of root node we want:

A a = abcdFactory.getInstance(A.class);

Furthermore, the same factory will build a tree starting with any other root. It's like an automatic puzzle factory that, given a starting piece and a box of pieces, will automatically add pieces until it constructs a puzzle. (It stops when there are no more places to attach pieces; there can be plenty of pieces left over.)

Before we can start building puzzles, we need to describe the pieces to Guice. We do that by implementing a method that specifies some constraints:

protected void configure() {
    bind(A.class).to(AImpl.class);
    bind(B.class).to(BImpl.class);
    bind(C.class).to(CImpl.class);
    bind(D.class).to(DImpl.class);
}

This specifies that there are four kinds of "slots" (A though D) into which four pieces can fit (AImpl through DImpl). The implementation classes are the four types of nodes from which we will build trees.

Next, Guice automatically figures out the shapes of the pieces: what other slots do they have where additional pieces should be attached?

It does this by searching for annotations using reflection. Each implementation class has either a no-arg constructor (in which case there there are no more slots to fill, resulting in a leaf node), or it has a constructor that's annotated with @Inject. In the tree, there will be a child for each parameter.

Here is a Guice-eye view of the classes AImpl through DImpl. All Guice cares about is that AImpl has two slots to fill, BImpl and CImpl have one, and DImpl has zero.

class AImpl implements A {
    @Inject AImpl(B b, C c) { ... }
    ...
}
 
class BImpl implements B {
    @Inject BImpl(D d) { ... }
    ...
}
 
class CImpl implements C {
    @Inject CImpl(D d) { ... }
    ...
}
 
class DImpl implements D {
    DImpl() {}
    ...
}

As a result, if Guice is asked to build an instance of A, the result is the graph in the picture at the top of this article. There are two different slots where D appears, so Guice has to create DImpl twice. From four possible types of node, it creates a tree with five nodes.

But Guice isn't limited to trees; it can build directed acyclic graphs and graphs with cycles as well. Here's another example where we build a graph with two cycles out of a pool of seven kinds of nodes. The methods named "backlink" cause additional edges to be created going upward, causing the cycles.

class AImpl implements A {
    @Inject AImpl(B b, C c) { ... }
}
 
class BImpl implements B {
    @Inject BImpl(F f, G g) { ... }
}
 
class CImpl implements C {
    @Inject CImpl(D d) { ... }
    @Inject void backlink(A a) { ... }
}
 
class DImpl implements D {
    @Inject DImpl(E e) { ... }
}
 
class EImpl implements E {
    @Inject EImpl(F f, G g) { ... }
    @Inject void backlink(C c) { ... }
}
 
class FImpl implements F {
    @Inject FImpl(G g) { ... }
}
 
class GImpl implements G {
    @Inject GImpl() { ... }
}
Guice graph with cycles

(Guice doesn't care what you name your methods. It looks for all methods with the @Inject annotation and adds their parameters as additional slots to fill. Guice will call these methods when putting together the graph, creating outgoing edges.)

Here's how we tell Guice about our pieces:

protected void configure() {
    bind(A.class).to(AImpl.class);
    bind(B.class).to(BImpl.class);
    bind(C.class).to(CImpl.class);
    bind(D.class).to(DImpl.class);
    bind(E.class).to(EImpl.class);
    bind(F.class).to(FImpl.class).in(Scopes.SINGLETON);
    bind(G.class).to(GImpl.class);
}

Why is F different? By default, Guice assumes you're trying to build a tree. There are two nodes with slots for F, so normally there would be two instances of FImpl, each with its own instance of G.

But setting a scope overrides this. We tell Guice that FImpl is a singleton, which means that it will create FImpl once and reuse it, resulting in two nodes pointing at the same instance of FImpl. (This is unnecessary for nodes that are part of a cycle. Rather than creating a tree of infinite size, Guice automatically links back to an ancestor node.)

Actually, it wasn't necessary to use backlink methods. If you define all the edges in your graph using constructors, Guice can still construct the graph! Since construction is from the bottom up, edges in the graph going upward are actually implemented using invisible proxy instances. The edge can be attached later, after its destination node is constructed.

(I think I would still put in the backlink methods, because otherwise it would be hard to connect nodes into cycles manually. You can avoid cycles while testing using mocks, but I'd rather not require users of my classes to always use Guice. Maybe there should be a mode where Guice warns about this? The use of proxies also means you need to careful not to violate encapsulation; a node can't cast an interface to its implementation because the cast will fail when there's a proxy. But this is a good idea anyway.)

There are also some interesting things you can do by hooking into the proxies, like adding logging; Guice uses proxies to implement a simple version of aspect-oriented programming.

There are a lot more features that you can read about in the User's Guide, and probably more that are yet undocumented. Much of what I learned above came from experimenting.

Knowing what Guice does doesn't tell you what it's actually good for, and what sort of program architecture you'll end up with if you use it in a real application. I'm still figuring that out and I'll write about it later.

[Update on May 26, 2007: the original version of this article implied that Guice works better if you bind interfaces to classes. That's not the case; both the type being bound and the implementation can be classes, and no subclass is necessary.]