Advanced Dagger Semantics
This series of documents describes the semantics of Dagger. It’s not intended for learning how to use Dagger, but rather for what Dagger is.
- Core Dagger
- Advanced Dagger (this document)
- Dagger Producers
Members-injection
In some environments, you may not have control over the construction of an object (for example, Android activities), but you’d like to inject data into the object nevertheless. Dagger provides a way to directly “inject into” fields and methods after an object is constructed.
A field annotated with @Inject
is an injectible field; these fields must
be non-final and non-private; a method annotated with @Inject
is an
injectible method; these methods must non-private.
A members-injectible class is a class with any injectible fields or methods
in it or any classes in its superclass chain. Its injectible fields and
methods are all of the injectible fields and methods in it or any superclass,
with one exception: if a superclass has an injectible method, and it is
overridden in a subclass without an @Inject
annotation, then it is not an
injectible method of the subclass.
For any members-injectible class, Dagger generates a synthetic binding with key signature as follows:
For each injectible field, the declared key of the field is the Dagger key consisting of an optional qualifier on the field, and the type of the field.
The declared key signature of a members-injectible class T
’s synthetic
binding has inputs as the declared keys of its injectible fields, together
with the declared dependency keys of its injectible methods, and output
MembersInjector<T>
. The order of input keys in the signature is unspecified.
The key signature is derived from the declared key signature as usual (by
“unwrapping” Provider
and Lazy
).
Note the API for MembersInjector<T>
is:
interface MembersInjector<T> {
void injectMembers(T instance);
}
Therefore, the binding logic of this synthetic binding returns a function; this function has the following behavior:
- For each injectible field, assign the field the value of the corresponding input key;
- For each injectible method, call the method with the arguments from the corresponding input keys.
Warning: this members-injection binding is not allowed to be injected by user code; it is only a figment of Dagger’s imagination.
Members-injection from entry points
Members-injection introduces a new type of entry point: If a class T
is
members-injectible, then a component may have a method with signature T ->
void
, e.g.
void injectT(T t);
This is a members-injection entry point with key MembersInjector<T>
.
Note: an entry point may, as per usual rules, just return a
MembersInjector<T>
, in which case it also is an entry point with key
MembersInjector<T>
, but just a normal entry point, not a members-injection
entry point.
Resolution and Runtime
Given a members-injection entry point with key MembersInjector<T>
, Dagger
generates an implementation that computes the MembersInjector<T>
by usual
rules, and then calls MembersInjector#injectMembers()
.
Here is a complete example of a members-injection entry point.
final class Foo {
@Inject Foo() {}
}
final class Bar {
@Inject Bar() {}
}
abstract class Base {
@Inject Foo foo;
}
final class Derived extends Base {
@Inject void bar(Bar bar);
}
@Component
interface C {
void injectDerived(Derived d);
}
The generated implementation of C
might look like:
final class DaggerC implements C {
void injectDerived(Derived d) {
d.foo = new Foo();
d.bar(new Bar());
}
}
Members-injection from @Inject
constructors
If a members-injectible class T
also has an @Inject
constructor, then Dagger
replaces the binding from that @Inject
constructor with a synthetic binding
as follows.
Suppose the declared key signature of the @Inject
binding was
@Q1 S1, ..., @Qn Sn -> T.
Then the synthetic binding’s signature is
MembersInjector<T>, @Q1 S1, ..., @Qn Sn -> T
Its binding logic is:
- call the constructor to construct a
T
- call
MembersInjector#injectMembers()
on the resultingT
Here is a complete example of a members-injection with an@Inject
constructor.
final class Foo {
@Inject Foo() {}
}
final class Bar {
@Inject Bar() {}
}
final class Baz {
@Inject Baz() {}
}
abstract class Base {
@Inject Foo foo;
}
final class Derived extends Base {
@Inject Derived(Baz baz) {}
@Inject void bar(Bar bar);
}
@Component
interface C {
Derived derived();
}
The generated implementation of C
might look like:
final class DaggerC implements C {
Derived derived() {
Derived d = new Derived(new Baz());
d.foo = new Foo();
d.bar(new Bar());
return d;
}
}
Warning: using members-injection with an @Inject
constructor is discouraged.
In general, members-injection makes it more difficult to test an object, since
just calling the constructor does not “fully” initialize the object. As long as
you’re allowed to define the constructor of an object, it’s usually better to
add the appropriate arguments to the constructor rather than relying on
members-injection.
Subcomponents
A subcomponent is an interface or abstract class annotated @Subcomponent
.
We often refer to both components and subcomponents as components, and when we want to make a distinction, we’ll refer to components (as defined previously) as top-level components.
The modules, entry points, and bindings of a subcomponent analogous to modules, entry points, and bindings of a component.
Note: subcomponents may not have component dependencies, because they do not have the appropriate field in their annotations. There’s no technical reason why they couldn’t; they just don’t.
The builder of a subcomponent is an interface or abstract class annotated
@Subcomponent.Builder
, that’s nested inside a subcomponent. Subcomponent
builders must follow the same rules as component builders.
The subcomponents of a component are:
- the subcomponents specified by the
Module#subcomponents
field on any module in its transitive closure - the subcomponents that are the return types of any entry points
- the subcomponents whose builders are the return types of any entry points
For example:
@Component(modules = M.class)
interface C {
S1 s1();
S2.Builder s2Builder();
}
@Module(subcomponents = S3.class)
final class M { ... }
@Subcomponent
interface S1 { ... }
@Subcomponent
interface S2 {
@Subcomponent.Builder
interface Builder { ... }
}
@Subcomponent
interface S3 { ... }
In the above example, the subcomponents of the top-level component C
are S1,
S2
, and S3
.
Note: Subcomponents do not specify the component they are children of; the dependency goes in the reverse direction. Because of this, subcomponents can be children of multiple components.
A top-level component forms a component tree, and it’s possible for
subcomponents to appear multiple times in this tree; these instances of
subcomponents are treated as distinct subcomponents, even though they’re
represented by the same class. For example, top-level component A
might have
subcomponents B
and C
, both of which have subcomponent D
; we view A
as
having two copies of D
in its component tree.
The component path of a subcomponent, in the context of a top-level component, is the path from the root to the subcomponent in that tree.
In addition to the bindings described earlier, components and subcomponents also have a synthetic binding for each of the component’s subcomponent’s builders, whose
- signature is
component -> subcomponent builder
; and - binding logic constructs a new subcomponent builder from the component.
Because subcomponents have access to all bindings in their parent components, constructing a subcomponent builder consists of creating a new object with a pointer to the parent component object.
Resolution and Runtime
When subcomponents are in the mix, Dagger amends the resolved graph as follows. Dagger constructs a graph for each top-level component, not for each subcomponent:
The nodes of the graph are:
- bindings and entry points of the component and all of its subcomponents, tagged by the component path;
- binding keys and dependency keys of those bindings, and all entry point keys, tagged by the component path of the binding or entry point.
Note: Because the same subcomponent class can appear multiple times in a component tree, bindings and entry points can as well, but they will be tagged differently, based on the component path.
The edges of the graph are directed edges consisting of:
- for each tagged binding, an edge from its tagged dependency keys to the tagged binding; this models dependencies of bindings;
- for each tagged entry point, an edge from the tagged entry point’s key to the tagged entry point; this models dependencies of entry points;
- for each tagged key
(K, P)
, whereK
is the key andP
is the component path, an edge from any tagged binding(B, Q)
, whereK
is the binding key forB
andQ
is a prefix ofP
; this models resolution of keys.
Note: This last condition describes the lexical scoping of Dagger subcomponents. To resolve a key in a subcomponent, we not only look for bindings in that subcomponent, but also in any of its ancestor components.
This graph can be thought of as a series of graphs layered on top of each other. First, the top-level component’s graph; then, the graph of its subcomponents; then, the graph of the subcomponents of those subcomponents, and so on. Most edges are within a single level of this graph, but lexical scoping adds edges from lower levels to higher levels.
Multibindings
Multibindings introduce a new kind of collection that allows multiple user-defined binding methods to contribute to the same collection.
Multibinding methods are either declarations or contributions. Multibinding declarations declare that a particular key is a multibinding:
- An optional binding declaration is an abstract method in a module
annotated
@BindsOptionalOf
. Its binding key is@Q Optional<T>
, assuming its declared binding key is@Q T
. - A multibinding declaration is an abstract method in a module annotated
@Multibinds
. Its binding key is its declared binding key, which must be one of@Q Set<T>
or@Q Map<K, V>
.
Contribution methods contribute elements into a multibinding collection:
- A set contribution binding is a
@Provides
or@Binds
binding method that is annotated either@IntoSet
or@ElementsIntoSet
. Suppose its declared binding key is@Q T
:- If a method is annotated
@IntoSet
, then its binding key is@Q Set<T>
. - If a method is annotated
@ElementsIntoSet
, thenT
must beSet<U>
for someU
.
- If a method is annotated
- A map contribution binding is a
@Provides
or@Binds
binding method that is annotated@IntoMap
and has a map key annotation, which is an annotation which itself is annotated@MapKey
. Suppose the contribution binding’s declared binding key is@Q T
. Then its binding key is@Q Map<K, T>
, whereK
is computed from the map key contribution as follows:
A simple map key annotation is an annotation with just a value
field, and
specifies a map key of the type of its value
field. Dagger provides several of
these, for example:
@MapKey
public @interface IntKey {
int value();
}
@MapKey
public @interface ClassKey {
Class<?> value();
}
A complex map key annotation is an annotation with @MapKey(unwrapValue =
false)
, and specifies a map key of the type of the map key annotation itself.
For example:
@MapKey(unwrapValue = false)
@interface MyKey {
String name();
Class<?> someClass();
int[] indices();
}
Here are some examples of multibinding methods and their binding keys:
// binding key = Map<Integer, String>
@Multibinds abstract Map<Integer, String> m();
// binding key = Optional<Foo>
@BindsOptionalOf abstract Foo foo();
// binding key = @Blue Set<Foo>
@Provides @IntoSet @Blue Foo blueFoo() { ... }
// binding key = Map<String, Foo>
@Binds abstract @IntoMap @StringKey("foo") Foo sfoo(FooImpl impl);
If a binding does not have a multibinding annotation, then it is called a unique binding.
Resolution and Runtime
A node in the resolution graph that represents a key is a multibinding node if either
- there is a multibinding declaration with that key;
- there is a multibinding contribution whose edge terminates in that key’s node.
Multibindings relax the restriction that every key node has exactly one inbound edge.
Optional bindings
If a node with key @Q Optional<T>
is a multibinding node, and there is a
binding with binding key @Q T
, then a synthetic binding is generated from @Q
T
to @Q Optional<T>
, with binding logic Optional.of(t)
.
A node representing an optional multibinding @Q Optional<T>
may have zero or
one inbound edges.
At runtime, to fulfill the request for Optional<T>
, if the node has zero
inbound edges, its value is Optional.empty()
, and otherwise (like normally,
using the synthetic binding above), it is an optional consisting of the single
inbound edge’s value.
Set or map multibindings
If any of the edges to a key node are set or map multibinding methods, then all inbound edges must be multibindings methods. If a map binding has several entries with the same map key, the graph is ill-formed.
At runtime, all multibinding methods’ logic is executed. For set bindings, the
results of each binding method are collected into a set, that is, throwing away
duplicates (using #equals()
on the resulting objects). For map bindings, the
results are paired with keys and collected into a map.
Map bindings are also allowed to be requested as Map<K, Provider<V>>
or
Map<K, Lazy<V>>
. In this case, the logic in the map binding contribution
methods is not executed when building this object, but rather deferred until
get()
is called on a value, similar to when Provider
or Lazy
are injected
normally.
Note about multibindings with subcomponents
Multibindings are accumulated across subcomponents. Since subcomponents
describe increasing lexical scopes, both multibinding contributions in a parent
component, as well as with multibinding contributions in a child component, are
available in the child component’s multibinding. Therefore, for example, if you
inject a Set<T>
multibinding in a child component, you might get a larger set
than if you inject the same set in a parent component.
Scopes
Scopes are Dagger’s way of caching the results of particular bindings; they declare that a binding’s logic may not be executed more than once within the context of a particular component.
A scope is a Java annotation that’s annotated @Scope
. For example,
@Scope
@interface RequestScope {}
defines a scope.
Bindings and components may be annotated with a scope:
The scope of a user-defined binding is an optional scope annotation applied to the binding method. It is an error to apply more than one scope to a binding.
The scopes of a component are zero or more scope annotations applied to the component.
There are two ways that Dagger validates scopes:
-
The scope on a binding must also be applied to the component that includes that binding.
-
Subcomponents may not have the same scope as any of their ancestor components.
Resolution and Runtime
Recall that a binding of a component corresponds to a node in the resolution graph, and it has an outgoing edge to its binding key. When a binding is scoped in a particular component, then that binding’s logic is executed at most once; the component object will cache the resulting value (that is, the output of the binding’s logic) and use the cached value whenever the binding’s key is subsequently requested.
This cache is thread-safe; that is, Dagger guarantees that the binding logic is executed at most once, even if two threads simultaneously request the binding’s key.
Interaction with Provider
and Lazy
If a binding is scoped, and its key @Q T
is requested by @Q Provider<T>
,
then each call to Provider#get
will return the same, scoped instance; Dagger
will always maintain the guarantee that the binding logic is executed at most
once.
If a binding is scoped, and its key @Q T
is requested by @Q Lazy<T>
, then
similarly, each call to Lazy#get
will return the same, scoped instance. (In
this case, of course, there’s no potential conflict in semantics, since both
Lazy
and scoping enforce a single execution of the binding logic.)
Reusable scope
Dagger defines a special scope called @Reusable
. This scope may be applied to
a binding, and it may not be applied to any component.
When a binding is @Reusable
scoped, Dagger will treat it as though it is
scoped in the least common ancestor of all components that use it.
For example, suppose the component hierarchy is (where ->
means “has a
subcomponent”, and the following describes a tree):
A -> B -> C -> D
-> E
-> F -> G
H -> I
and suppose that a @Reusable
binding is used in D
and G
. Then the binding
is treated as being scoped in B
.