Watching a talk from Square's CTO Bob Lee, I just learned about Dagger, a new dependency injection framework for Java and Android which is currently in the works at Square, Inc.
Considering the number of existing DI solutions in the Java space e.g. CDI, Google Guice and Spring one might wonder whether the world really needs yet another DI framework. According to Bob's talk, Dagger (a pun on "directed acyclic graph") is the attempt to create a modern and fast DI framework based on the insights gained during development and usage of Guice (Bob was the founder of the Guice project at Google). And indeed does Dagger come up with some quite interesting ideas which I'd like to discuss in more detail in the following.
Overview
Dagger is centered around the annotations for dependency injection defined by JSR 330 (which Bob Lee co-led). This is a good thing because it increases portability of your code between different DI solutions.
Dependencies are retrieved by annotating fields or constructors with @Inject
:
1 2 3 4 5 6 7 8 9 10 11 |
public class Circus { private final Artist artist; @Inject public Circus(Artist artist) { this.artist = artist; } //... } |
To satisfy dependencies, Dagger creates the required objects using their @Inject
-annotated constructor (in turn creating and passing any dependencies) or the default no-args constructor.
Where that's not possible (e.g. when an implementation of an interface needs to be injected) provider methods can be used. Provider methods must be annotated with @Provides
and be defined in a class annotated with @Module
like this:
1 2 3 4 5 6 |
@Module public class CircusModule { @Provides Artist provideArtist() { return new Juggler(); } } |
The @Module annotation is also used to define the entry point of an application:
1 2 3 4 |
@Module( entryPoints=CircusApp.class ) public class CircusModule { //... } |
This entry point represents the root of the object graph managed by Dagger. As we'll see in a moment, explicitly defining the root allows for compile-time validation of the dependency graph. An instance of the entry point type can be retrieved from the ObjectGraph
class, passing the module(s) to create the graph from:
1 2 3 |
ObjectGraph objectGraph = ObjectGraph.create(new CircusModule());
CircusApp circus = objectGraph.get(CircusApp.class);
circus.startPerformance();
|
Dagger also also provides support for qualifiers, lazy injection, injection of providers and more. The project's web site gives a good overview. Apart from that it's interesting to see what Dagger deliberately does not support to avoid an increased complexity:
- Circular dependencies between objects
- Method injection
- Custom scopes (Objects are either newly created for each injection or singleton-scoped)
Code generation
DI frameworks usually make intensive use of reflection to examine annotations, find injection points, create managed objects etc. While reflection today isn't as expensive as it used to be in earlier years, it still can take a considerable amount of time to create large object graphs with lots of dependencies.
Dagger tries to improve upon that with the help of code generation. It provides a JSR 269 based annotation processor which is used at compile time to create an adapter class for each managed type. These adapter classes contain all the logic required at run time to set up the object graph by invoking constructors and populating references to other objects, without making use of reflection.
This approach promises performance benefits over reflection-based ways for creating object graphs typically used by DI frameworks. On my machine Dagger needed roughly half the time to initialize the graph of the CoffeeApp example using the generated classes compared to using reflection (which it also supports as fallback). Of course this is by no means a comprehensive benchmark and can't be compared with other frameworks but it surely shows the potential of the code generation approach.
The annotation processor also performs a validation of the object graph and its dependencies at compile time. So if for instance no matching type (or more than one) can be found for a given injection point, the build will fail with an error message describing the problem. This helps in reducing turn-around times compared to discovering this sort of error only at application start-up. Implementing this sort of checks using an annotation processor makes them available in IDEs (which typically can integrate annotation processors) as well as headless builds, e.g. on a CI server.
Object graph visualization
Not mentioned in the documentation, Dagger also provides an annotation processor which generates a GraphViz file visualizing the object graph. This may be useful to get an understanding of unknown object graphs. The following shows the graph from the CoffeeApp example:
Summary
Dagger is a new dependency injection framework for Java and Android.
While it's still in the works (the current version is 0.9 and there are still some apparent bugs), I find the concept of using an annotation processor for validating the object graph at compile time and generating code for a faster initialization at runtime very interesting. In particular on mobile devices fast start up times are essential for a good user experience.
I also like the idea of leaving out features which might provide some value but would add much complexity. One thing I'm missing though is some sort of interceptor or decorator mechanism. This would be helpful for implementing typical cross-cutting concerns.
It'll definitely be interesting to see how the code generation approach works out in practice and whether other DI solutions possibly adapt that idea.