days
1
5
hours
1
2
minutes
2
1
seconds
0
7
search
Tutorial

Dependency injection with the Dagger framework

Sven Ruppert
Dagger image via Shutterstock

Dagger aficionado Sven Ruppert demonstrates dependency injection in a simple example aimed at declaring dependencies, specifying how to satisfy them, as well as allowing you to focus on the interesting classes.

Dagger is an open source dependency injection (DI) framework based on Guice. However, the developers of Dagger weren’t satisfied with the basic principle of Guice: Again and again they had to write in larger projects, which involved tonnes of binding code. Since this was part of the static semantics, their strategy was to extract this binding and store it in source code form.

The Dagger developers also wanted something that was easy to read and that supports the debugging process. Below we look more closely at Dagger and start exploring the framework. Our examples are based on Dagger version 1.2.

Dependency injection with Dagger

One of the quirks of Dagger is its availability in both Java and Android systems. Let’s look at a simple example and start with an easy @Inject instance (Main Service) in our Main class:

public class Main {
  @Inject MainService mainService;
  /..
}
public interface MainService {
  public String execute(String txt);
}
public class MainServiceImpl implements MainService {
  public String execute(String txt){
    return "MainServiceImpl _ " + txt;
  }
}

In order to obtain an instance of a class with injected attributes, the use of ObjectGraph is required. ObjectGraph is the representation of all dependencies which are necessary for instantiation. In the following example, we can see the initialization and use of Dagger in the Main class:

public class Main {
 @Inject MainService mainService;

  public static void main(String[] args) {
    //bootstrapping
    ObjectGraph objectGraph
        = ObjectGraph.create(new BusinessModule());
    Main main = objectGraph.get(Main.class);
//    Main main = new Main();
//    objectGraph.inject(main);
    System.out.println("execute = " + main.mainService.execute("Go"));
    System.out.println("execute = " + main.mainService.execute("Go"));
    System.out.println("execute = " + main.mainService.execute("Go"));
  }
}

There are two ways that Dagger accesses the instance of a class that contains the injected attributes. One way is through the method get(..) of the class ObjectGraph, which directly delivers a ready instance. The other way is through an explicit call to the method inject(..) which can be applied to existing instances. This allows you to control the time between the generation of the holding class instance and the member itself.

Something to note: In order to generate an instance of the class ObjectGraph, a parameter of the method create(..) or a module is required. In our case, we’ve created BusinessModule(). In this module dependencies among themselves are defined, and the producer is also used.

@Module(library = false, injects = {Main.class}, complete = true)
public class BusinessModule {
  @Provides
  MainService provideMainService() {
    return new MainServiceImpl();
  }
}

The producer can be generated either in the instance itself, or you can use an injection on method parameters (below). Note that Dagger must either use the new constructor – if the instance to be created itself has no attributes that are injected – or have passed an instance via method parameters with injected attributes.

@Provides
MainService provideMainService(MainServiceImpl mainService) {
  return mainService;
}

Internal functions

The basic question that developers are quick to ask involves the definition of dependencies. There are several ways in Java to define the dependencies – if the dependencies are quite clear, then it’s usually easy. When distinctions or multiplicities become involved, it becomes more complicated.

Take for instance an interface like SubService. For this interface, there are two implementations: SubServiceA and SubServiceB. If we place @Inject SubService in the source code, the implementation isn’t clear. We assume in the following that this will always generate an instance, a corresponding method provideXXX (in our example provideMainService ()) that has been administered with the annotation @Produces. This is a factory method.

public class MainServiceImpl implements MainService {
  @Inject SubService subService;
  public String execute(String txt){
    return subService.work(txt);
  }
}

A factory method for creating the instance of SubService is also required. Here you can choose the implementation (SubServiceA or SubServiceB) that should be used. This is, of course, a static decision. At a later stage we will make it dynamic.

@Provides
SubService provideSubService() {
  return new SubServiceA();
}

Reflection and configuration

But does Dagger recognise which instance is to be used where? Reflection or configuration is the answer here. In some frameworks, the configuration approach is used; this configuration is then read and used as the basis of the instantiation. Unfortunately, such approaches can be problematic as they are cumbersome and error-prone.

The alternative is Reflection. At runtime, we can determine which class has the dependencies, however, we have yet to resolve the multiplicities in this case. JSR-330 has the qualifier @Named(..) for this purpose. This will allow you to enter the respective implementation names, which are then used in the resolution. SubServiceA will be implemented as @Named(“A”) and SubServiceB with @Named(“B”). Once you specify the qualifier, we have defined @Inject:

@Inject @Named("A") SubService subservice;

The accompanying factory method is also provided with @Named(“A”). It’s all about combining class / interface and annotation followed by matching:

@Inject SubService subservice

You mustn’t forget that you’ll still be required to write a factory method without qualifiers.

@Module(library = false, injects = {Main.class},complete = true)
public class BusinessModule {
  @Provides
  MainService provideMainService(MainServiceImpl mainService) {
    return mainService;
  }
  @Provides @Named("A")
  SubService provideSubServiceA() {
    return new SubServiceA();
  }
  @Provides @Named("B")
  SubService provideSubServiceB() {
    return new SubServiceB();
  }
  @Provides
  SubService provideSubService(@Named("A") SubService subServiceA) {
    return subServiceA;
  }
}

How and when are dependencies now detected? This can be accomplished at runtime using Reflection. Dagger is a slightly different framework: Reflection is used, but it isn’t a runtime, rather, it’s engaged in the compile process. The dependencies are resolved using Reflection and checked, with this process producing different results. Firstly, a file with the extension dot is applied, where you can read about the dependencies found. See the following:

digraph G1 {
  concentrate = true;
  Main -> MainService;
  MainService -> MainServiceImpl;
  MainServiceImpl -> SubService;
  n2 [label="@javax.inject.Named(value=A)
           /org.rapidpm.demo.dagger.business.subservice.SubService"];
  SubService -> n2;
}

The compounds are easy to recognize. But what classes are generated? This refers to: Main$$InjectAdapter, MainServiceImpl$$InjectAdapter and BusinessModule$$ModuleAdapter.

Let’s start with the Main$$InjectAdapter class. The adapter provides a get() method, which returns an already instantiated reference. Here you can see the exact Lifecycle – and it’s quite manageable. It consists of two steps: Firstly, the instance is created, and secondly, the injectMembers(..) method is incorporated.

In addition, the compound is visable to other classes. To ensure that everything remains in static semantics, and at runtime, a corresponding Getter is called upon. Compared to Reflection, this approach is much more efficient at runtime. Thus, this adapter class forms the complete binding that includes all combinations and variants of the applications.

Author
Sven Ruppert
Sven Ruppert has been coding Java since 1996. He is a Oracle DeveloperChampions, Developer Advocate at Vaadin and Speaker, Helping developers world-wide to grow their business.. In his free time he regularly contributes to German IT periodicals, including Java Magazin and Entwickler Magazin, as well as tech portals such as jaxenter.de

Leave a Reply

Be the First to Comment!

avatar
400
  Subscribe  
Notify of