-
Notifications
You must be signed in to change notification settings - Fork 115
Scopes
A scope is one of the most important concept in ToothPick. It is actually important in Dependency Injection at large but Toothpick clearly exposes it to developers.
In the JSR 330, scopes are mentioned as :
A scope [..] governs how the injector reuses instances of the type. By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it. If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection.
But let's be a bit more precise...
In ToothPick, injections and instance creations always take place in a given scope.
Let's consider the following example of injection :
//a class using field and method injections
class Foo {
@Inject Bar bar;
@Inject void setQurtz(Qurtz qurtz) {...}
}
//injecting an object in a scope
Toothpick.inject(new Foo(), scope);
The statement above is an entry point of a dependency graph in ToothPick. It will inject new Foo()
, which means that :
- all
@Inject
annotated fields of the instance ofFoo
will be assigned; - all
@Inject
annotated methods of the instance ofFoo
will be called;
In both cases, the values of the injected field and the parameters of the injected methods will be created inside the scope scope
and its parents. Note that all transitive dependencies of Bar
and Qurtz
will also be created if needed, also in the scope scope
and its parents.
Let's consider the following example of instance creation :
//a class using field and method injections
class Foo {
@Inject Foo() {...}
@Inject Bar bar;
@Inject void setQurtz(Qurtz qurtz) {...}
}
//injecting an object in a scope
Toothpick.getInstance(Foo.class, scope);
The statement above is also an entry point of a dependency graph in ToothPick. It will create an instance of the class Foo
and inject all its members as in the injection case above. If ToothPick creates an instance, it will always inject its dependencies.
A scope contains bindings & scoped instances :
-
a binding : is way to express that a class
Foo
is bound to an implementationBar
, which we denoteFoo --> Bar
. It means that writing@Inject Foo a;
will return aBar
. Bindings are valid for the scope where there are defined, and are inherited by children scopes. Children scopes can also override any binding inherited from of a parent. -
scoped instances : a scoped instance is an instance that is reused for all injection of a given class. Scoped instances are "singletons" in their scope, and children scopes. Not all bindings create scoped instances. Binding
Foo.class
to an instance or to aProvider
instance means that those instances will be recycled every time we inject {@code Foo} from this scope and children scopes. Note that in the case of the provider, the provider itself is recycled, not the instances it produces.
We will see XXX, how to define bindings and scoped instances in Toothpick.
In Toothpick, Scopes create a tree (actually a disjoint forest, a.k.a a directed acyclic graph). Each scope can have children scopes.
//Example of scopes during the life of an Android application
Application Scope
/ \ \
/ \ \
Activity Scope \ Service 2 Scope
/ \
/ Service1 Scope
/
Fragment Scope
Operations on the scope tree (adding / removing children, etc.) should be performed via the ToothPick
class that wraps these operations.
Scopes have a name, which can be any object. Here is the code to create the scope tree above.
ToothPick.openScopes(application, activity, fragment);
ToothPick.openScopes(application, service1);
ToothPick.openScopes(application, service2);
It's intuitive to use the objects having a life cycle as names. When the object starts its life cycle, create a scope ToothPick.openScope(object)
and when the object dies, close its associated scope ToothPick.closeScope(object)
.
Opening multiple Scopes is possible, the opened scopes are then the children from each other, in order. This method will return the last open scope.