1. Introduction
In this tutorial, we’ll take a look at Dagger 2 – a fast and lightweight dependency injection framework.
The framework is available for both Java and Android, but the high-performance derived from compile-time injection makes it a leading solution for the latter.
2. Dependency Injection
As a bit of a reminder, Dependency Injection is a concrete application of the more generic Inversion of Control principle in which the flow of the program is controlled by the program itself.
It’s implemented through an external component which provides instances of objects (or dependencies) needed by other objects.
And different frameworks implement dependency injection in different ways. In particular, one of the most notable of these differences is whether the injection happens at run-time or at compile-time.
Run-time DI is usually based on reflection which is simpler to use but slower at run-time. An example of a run-time DI framework is Spring.
Compile-time DI, on the other hand, is based on code generation. This means that all the heavy-weight operations are performed during compilation. Compile-time DI adds complexity but generally performs faster.
Dagger 2 falls into this category.
3. Maven/Gradle Configuration
In order to use Dagger in a project, we’ll need to add the dagger dependency to our pom.xml:
<dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger</artifactId> <version>2.16</version> </dependency>
Furthermore, we’ll also need to include the Dagger compiler used to convert our annotated classes into the code used for the injections:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <annotationProcessorPaths> <path> <groupId>com.google.dagger</groupId> <artifactId>dagger-compiler</artifactId> <version>2.16</version> </path> </annotationProcessorPaths> </configuration> </plugin>
With this configuration, Maven will output the generated code into target/generated-sources/annotations.
For this reason, we likely need to further configure our IDE if we want to use any of its code completion features. Some IDEs have direct support for annotation processors while others may need us to add this directory to the build path.
Alternatively, if we’re using Android with Gradle, we can include both dependencies:
compile 'com.google.dagger:dagger:2.16' annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
Now that we have Dagger in our project, let’s create a sample application to see how it works.
4. Implementation
For our example, we’ll try to build a car by injecting its components.
Now, Dagger uses the standard JSR-330 annotations in many places, one being @Inject.
We can add the annotations to fields or the constructor. But, since Dagger doesn’t support injection on private fields, we’ll go for constructor injection to preserve encapsulation:
public class Car { private Engine engine; private Brand brand; @Inject public Car(Engine engine, Brand brand) { this.engine = engine; this.brand = brand; } // getters and setters }
Next, we’ll implement the code to perform the injection. More specifically, we’ll create:
- a module, which is a class that provides or builds the objects’ dependencies, and
- a component, which is an interface used to generate the injector
Complex projects may contain multiple modules and components but since we’re dealing with a very basic program, one of each is enough.
Let’s see how to implement them.
4.1. Module
To create a module, we need to annotate the class with the @Module annotation. This annotation indicates that the class can make dependencies available to the container:
@Module public class VehiclesModule { }
Then, we need to add the @Provides annotation on methods that construct our dependencies:
@Module public class VehiclesModule { @Provides public Engine provideEngine() { return new Engine(); } @Provides @Singleton public Brand provideBrand() { return new Brand("Baeldung"); } }
Also, note that we can configure the scope of a given dependency. In this case, we give the singleton scope to our Brand instance so all the car instances share the same brand object.
4.2. Component
Moving on, we’re going to create our component interface. This is the class that will generate Car instances, injecting dependencies provided by VehiclesModule.
Simply put, we need a method signature that returns a Car and we need to mark the class with the @Component annotation:
@Singleton @Component(modules = VehiclesModule.class) public interface VehiclesComponent { Car buildCar(); }
Notice how we passed our module class as an argument to the @Component annotation. If we didn’t do that, Dagger wouldn’t know how to build the car’s dependencies.
Also, since our module provides a singleton object, we must give the same scope to our component because Dagger doesn’t allow for unscoped components to refer to scoped bindings.
4.3. Client Code
Finally, we can run mvn compile in order to trigger the annotation processors and generate the injector code.
After that, we’ll find our component implementation with the same name as the interface, just prefixed with “Dagger“:
@Test public void givenGeneratedComponent_whenBuildingCar_thenDependenciesInjected() { VehiclesComponent component = DaggerVehiclesComponent.create(); Car carOne = component.buildCar(); Car carTwo = component.buildCar(); Assert.assertNotNull(carOne); Assert.assertNotNull(carTwo); Assert.assertNotNull(carOne.getEngine()); Assert.assertNotNull(carTwo.getEngine()); Assert.assertNotNull(carOne.getBrand()); Assert.assertNotNull(carTwo.getBrand()); Assert.assertNotEquals(carOne.getEngine(), carTwo.getEngine()); Assert.assertEquals(carOne.getBrand(), carTwo.getBrand()); }
5. Spring Analogies
Those familiar with Spring may have noticed some parallels between the two frameworks.
Dagger’s @Module annotation makes the container aware of a class in a very similar fashion as any of Spring’s stereotype annotations (for example, @Service, @Controller…). Likewise, @Provides and @Component are almost equivalent to Spring’s @Bean and @Lookup respectively.
Spring also has its @Scope annotation, correlating to @Singleton, though note here already another difference in that Spring assumes a singleton scope by default while Dagger defaults to what Spring developers might refer to as the prototype scope, invoking the provider method each time a dependency is required.
6. Conclusion
In this article, we went through how to set up and use Dagger 2 with a basic example. We also considered the differences between run-time and compile-time injection.
As always, all the code in the article is available over on GitHub.