-
Notifications
You must be signed in to change notification settings - Fork 113
Modules & Bindings
Modules define bindings.
Setting up the bindings in a scope is performed via installation of modules in a scope. A module defines a set of bindings.
Scope s = ToothPick.openScope(obj);
s.installModules(new Module() {{
bind(IFoo.class).to(new Foo());
bind(IBar.class).to(Bar.class);
}});This will setup the 2 bindings in the scope s.
Graphically, we represent this situation by:
Scope s : IFoo --> new Foo & IBar --> Bar
Now, let's define:
class A {
@Inject IFoo foo1;
@Inject IFoo foo2;
@Inject IBar bar1;
@Inject IBar bar2;
}and we inject it in scope s defined above :
A a = new A();
ToothPick.inject(a, s);then :
-
a.foo1will be the instance ofFoocreated in the module above; -
a.foo2will be the same instance ofFoo; -
a.bar1anda.bar2will be both be instances ofBar.class, and will be different from each other.
This is true for any scope that is a children of s. Except if the child scope of s overrides any binding of s.
The class toothpick.config.Module defines a small DSL to conveniently express bindings, and bindings can be expressed in various ways, which are called 'Binding modes' :
class SimpleModule extends Module {
SimpleModule() {
bind(IFoo.class).to(Foo.class); // case 1
bind(IFoo.class).to(new Foo()); // case 2
bind(IFoo.class).toProvider(FooProvider.class); // case 3
bind(IFoo.class).toProvider(new FooProvider()); // case 4
bind(Foo.class); // case 5
}
}The various binding modes are :
- case 1: Every
@Inject IFoowill be assigned a new instance ofFoo. - case 2: Every
@Inject IFoowill be assigned the same instance ofFoo. The instance defined in the module. - case 3: Every
@Inject IFoowill be assigned a new instance ofFooproduced by a new instance ofFooProvider. - case 4: Every
@Inject IFoowill be assigned a new instance ofFooproduced by the same instance ofFooProvider. The instance defined in the module. - case 5 : Every
@Inject Foowill be assigned a new instance ofFoo.
It is possible to name injections & bindings in ToothPick to distinguish 2 bindings that bind the same class. The name used to qualify an injection or a binding can be either:
- a string
- an annotation class
Using an annotation class as a qualifier is the exact same thing as using a string with the value of the Fully Qualified Name of the annotation.
Qualified injections can be used when injecting :
- fields
- methods or constructor parameters
- dynamically, via
TootPick.getInstance(Foo.class, "name")
Example :
bind(IFoo.class).to(new Foo()) // un named binding
bind(IFoo.class).withName("name").to(new Foo()) // named binding, with name "name"
bind(IFoo.class).withName(my.MyAnnotation.class).to(new Foo()) // named binding, with name "@my.MyAnnotation"
bind(IFoo.class).withName("my.MyAnnotation").to(new Foo()) // named binding, with name "my.MyAnnotation"
//the 2 last bindings are equivalent and require the annotation :
package my;
@Qualifier
@interface MyAnnotation {}to use these bindings, one would use
@Inject IFoo foo; //use the unnamed binding
TootPick.getInstance(IFoo.class, "name") // named binding, with name "name"
@Inject @my.MyAnnotation IFoo foo; // named binding, with name "@my.MyAnnotation"
TootPick.getInstance(IFoo.class, "my.MyAnnotation") // named binding, with name "@my.MyAnnotation"In the example above, in the cases 1, 3 and 5, ToothPick is responsible for creating the instances during injection. Whereas in cases 2 and 4, the developer herself is creating the instances of Foo, either directly or via a provider that she defines.
A good rule to remember is :
As soon as ToothPick creates an object, its dependencies will be injected.
which also implies that :
As soon as a developer creates an object with ToothPick, she has to take care of injecting this object dependencies.
This means that if we define the class :
class Foo {
@Inject Scope s;
}In the cases 1, 3 and 5, all instances of Foo that are created during injection by ToothPick will themselves be injected. All injected methods and fields will be called/assigned and their annotated constructor or the default constructor will be used to create the instances of Foo.
In the case 2 and 4, the developer will have to assign the field s to a scope, the developer can do it manually or by asking ToothPick to do it : ToothPick.inject(foo, s).
By default, scopes use the bindings defined in parent scopes. But they are allowed to override them. Such an override will impact the scope itself and its own children, hiding the binding defined in the parent.
Here is an example :
Scope S0 : IFoo.class --> Foo.class
\
\
Scope S1 : IFoo.class --> Foo2.class
\
\
Scope S2
then if we have a class
class A {
@Inject IFoo foo;
}Then
ToothPick.inject(a, s0); // => a.foo is an instance of Foo1
ToothPick.inject(a, s1); // => a.foo is an instance of Foo2
ToothPick.inject(a, s2); // => a.foo is an instance of Foo2Example As an example, in all scopes s of Toothpick, there is always a binding of the toothpick.Scope class to s (as a singleton of scope s). This binding is overridden by all scopes.
Scope S0 : Scope.class --> S0
\
\
Scope S1 : Scope.class --> S1