When you use Dependency Injection, you may want to create objects that have different life-cycles. As an example, you may want some objects to live as long as the application lives, and some to live only for a short duration, like only for the duration of a request. Such objects can be referred to as Scoped Objects. In this article, I will show how Dagger 2 provides the following functionality in relation to Scoped Objects:
- Defining the Scoped Objects
- Relating these Scoped Objects (using Components and Subcomponents)
- Creating objects of one Scope from a related Scope
Dagger 2 is a dependency injection framework. Unlike other frameworks, which use reflection, it provides dependency injection functionality by generating code which makes it performant (Java reflection performance cost) and traceable. One of the greatest advantages of Dagger 2 above other frameworks is the object graph validation during compilation as opposed to runtime validation in other frameworks.
From this point onwards in the article, I will refer Dagger 2 as just Dagger. Also the code for the sample below can be found here (link to the sample code).
Defining the Scoped Objects
The definition of Scoped Objects can be broken into two steps. Firstly, defining the custom scopes and secondly, using
these custom scopes to make Scoped Objects. Dagger uses several annotations from javax.inject
to provide the dependency injection functionality. In the code samples below @Scope, @Inject and @Named
annotations are from the javax.inject package.
Defining Custom Scopes
The Scope annotation is used to define the scopes.
// objects marked with this will live for lifetime of application
@Scope
public @interface ApplicationScoped {}
// objects marked with this will live for lifetime of a request
@Scope
public @interface RequestScoped {}
Using Custom Scopes on Objects
From the code sample below the following observations can be made:
- Scopes are applied on classes.
- Scopes can also be applied on provider methods (methods annotated with
@Provideson classes annotated with@Module). GreetingProviderisApplicationScoped.Greeterand@Named("Visitor") StringareRequestScoped.
@ApplicationScoped // <<<<<<
public class GreetingProvider {
@Inject
public GreetingProvider() { }
public String get() { return "Hello"; }
}
@RequestScoped // <<<<<<
public class Greeter {
private final String visitor;
private final GreetingProvider greetingProvider;
@Inject
public Greeter(GreetingProvider greetingProvider,
@Named("Visitor") String visitor) {
this.visitor = visitor;
this.greetingProvider = greetingProvider;
}
public String greet() {
return greetingProvider.get() + " " + visitor;
}
}
@Module
public class GreeterModule {
private final String visitor;
public GreeterModule(String visitor) { this.visitor = visitor; }
@Provides @RequestScoped @Named("Visitor") // <<<<<<
public String provideVisitor() { return visitor; }
}
A Note on @Inject, @Module and @Provides
- Annotating a constructor of an object with
@Injectinstructs Dagger to create that object and inject dependencies in it.GreetingProviderhas a no-argument constructor with the Inject annotation. Dagger will just create this object.Greeterhas a two argument constructor. That means Dagger will create the object and also ensure the dependencies are already created before the construction of Greeter.
- Classes annotated with
@Modulecontribute objects to object graph using methods annotated with@Provides. These methods are called as Provider Methods. These are useful in cases where objects cannot be created by Dagger. Like when libraries/frameworks provide those objects. In the case above @Inject cannot be put on the constructor ofString. Hence the String can be provided using the provider method. To differentiate this String from other Strings in the application@Namedhas been used.
Relating and Creating the Scoped Objects
Dagger provides two ways of establishing relationships between different scopes. You can define the relationships
by either using Component Dependencies or Subcomponents. I will show how both of these can be used to relate
and create the scopes defined above. But before that lets understand how Dagger uses Components and
Subcomponents.
What is a Component in Dagger
Component is defined using the @Component annotation on an interface. When Dagger generates code, it generates
an implementation for this interface and this implementation acts as a Factory for creating objects. As an example,
if there is an interface named ApplicationComponent that has the annotation, Dagger will generate an implementation
named DaggerApplicationComponent which will be used to construct objects. We will see this in detail in the
subsequent sections.
What is a Subcomponent in Dagger
Like Component, Subcomponent is defined using @Subcomponent annotation on an interface. The difference is that
it cannot live standalone and has to be defined as a method on an interface marked as Component and that method
should return the interface marked as Subcomponent. This means that the Subcomponent interface
implementation can only be gotten using the parent Component’s implementation. We will see this in detail in subsequent
sections.
Subcomponents
First we will see, in the following two sub-sections, how the Scoped Objects are related and created when using Subcomponents.
Relating the Scoped Objects
In the code below the following points are worth noting:
ApplicationComponentis defined asComponentand has ApplicationScope.RequestComponenthas been defined as aSubcomponentand has RequestScope.- RequestComponent has been defined as a method on ApplicationComponent interface. The Subcomponent will have to be defined as a method on the Component interface definition to which it relates. We can say that there is a parent-child relationship between ApplicationComponent and RequestComponent.
- Unlike in component dependencies, which we will see in next section, the
GreetingProviderdoes not have to be defined on ApplicationComponent even though it is being used inGreeter. The objects from parent scope are visible to the child scope.
@ApplicationScoped
@Component
public interface ApplicationComponent {
Controller controller(); // controller will be discussed in next section
RequestComponent requestComponent(GreeterModule greeterModule); // <<<<<<
}
@RequestScoped
@Subcomponent(modules = GreeterModule.class) // <<<<<<
public interface RequestComponent {
Greeter greeter();
}
Creating the Scoped Objects
In the code sample below:
Controllerhas an application scope.- Its
handleRequestmethod delegates the request handling toGreeter’sgreetmethod. - As Greeter has a request scope, different from that of Controller, it has to be created using the RequestComponent, the instance of which is created from ApplicationComponent. This is why ApplicationComponent has been injected into the Controller. Components can also be injected into objects, but only those that have same scope defined as that on the object.
- The
Controlleritself is created usingDaggerApplicationComponentwhich Dagger has generated.
@ApplicationScoped
public class Controller {
private final ApplicationComponent applicationComponent;
@Inject
public Controller(ApplicationComponent applicationComponent) {
this.applicationComponent = applicationComponent;
}
public String handleRequest(String name) {
RequestComponent requestComponent = applicationComponent
.requestComponent(new GreeterModule(name));
return requestComponent.greeter().greet();
}
}
public class App {
public static void main(String[] args) {
Controller controller = DaggerApplicationComponent.create().controller();
System.out.println(controller.handleRequest("Hello")); // prints Hello World!
}
}
Component Dependencies
Now we will see how the Scoped Objects are related using Component Dependencies and how they are created.
Relating the Scoped Objects
In the code below the following points are worth noting:
- Both
ApplicationComponentandRequestComponentare declared asComponent. - The component definition on RequestComponent uses the
dependenciesattribute to define its relationship to ApplicationComponent. - Also note that ApplicationComponent has to define
GreetingProvideras it is used byGreeter. The objects which are used in other related scopes have to be defined on the Component definition interface.
@ApplicationScoped
@Component
public interface ApplicationComponent {
Controller controller(); // controller will be discussed in next section
GreetingProvider greetingProvider(); // <<<<<<
}
@RequestScoped
@Component(
dependencies = ApplicationComponent.class, // <<<<<<
modules = GreeterModule.class)
public interface RequestComponent {
Greeter greeter();
}
Creating the Scoped Objects
In the code sample below:
Controllerhas an application scope.- Its
handleRequestmethod delegates the request handling toGreeter’sgreetmethod. - As Greeter has a request scope, different from that of Controller, it has to be created using the RequestComponent.
DaggerRequestComponent, which is generated by Dagger, is used here to create RequestComponent. As ApplicationComponent has been defined as dependencies, as shown above, an instance of it is required while building the RequestComponent. This is why ApplicationComponent has been injected into the Controller. - The
Controlleritself is created usingDaggerApplicationComponentwhich Dagger has generated.
@ApplicationScoped
public class Controller {
private final ApplicationComponent applicationComponent;
@Inject
public Controller(ApplicationComponent applicationComponent) {
this.applicationComponent = applicationComponent;
}
public String handleRequest(String name) {
RequestComponent requestComponent = DaggerRequestComponent.builder()
.applicationComponent(applicationComponent)
.greeterModule(new GreeterModule(name))
.build();
return requestComponent.greeter().greet();
}
}
public class App {
public static void main(String[] args) {
Controller controller = DaggerApplicationComponent.create().controller();
System.out.println(controller.handleRequest("Hello")); // prints Hello World!
}
}
Summary
Dagger has full support for defining the Scoped Objects. Subcomponets and Component Dependencies are used by Dagger to relate the Scoped Objects. Dagger generates Factories that are used to create the Scoped Objects.