Skip to content

Modules & Bindings

Stéphane Nicolas edited this page May 7, 2016 · 23 revisions

Modules define bindings.

The class toothpick.config.Module defines a small DSL to conveniently express bindings, and bindings can be expressed in various ways :

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 IFoo will be assigned a new instance of Foo.
  • case 2: Every @Inject IFoo will be assigned the same instance of Foo. The instance defined in the module.
  • case 3: Every @Inject IFoo will be assigned a new instance of Foo produced by a new instance of FooProvider.
  • case 4: Every @Inject IFoo will be assigned a new instance of Foo produced by the same instance of FooProvider. The instance defined in the module.
  • case 5 : Every @Inject Foo will be assigned a new instance of Foo.

Bindings, instance creation and injections

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, it will be injected.

which also implies that :

As soon as a developer creates an object with ToothPick, the developer 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 ).

Scoped and unscoped bindings

In toothpick there are 2 kinds of bindings :

  • unscoped bindings
  • scoped bindings

In the 2 following sections, we will use a common example class and see how scoped and unscoped bindings differ when injecting it :

class A {
  @Inject IFoo foo1;
  @Inject IFoo foo2;
  @Inject IBar bar1;
  @Inject IBar bar2;
}

Unscoped Bindings

bind(IFoo.class).to(Foo.class) is a simple association IFoo --> Foo. Unscoped bindings are inherited by children scopes and can be [overridden in children scopes](#Binding overrides in children scopes). An unscoped binding expresses no constraints on the creation of the Foo instances, as opposed to a scope binding. An unscoped binding is said to belong to a given scope.

Basically, when a scope defines bind(IFoo.class).to(Foo.class), it means that in this scope and its children scopes : @Inject IFoo foo will return a Foo.

Example(s) :

Suppose we define the following scopes :

Scope s0 
  \
   \
  Scope S1 : IFoo --> new Foo  & IBar --> Bar
    \
     \
    Scope S2 

Then using the class A defined above, we would have :

  • Toothpick.inject(new A(), S0) : There is no binding to inject either IFoo or IBar as the bindings are only defined in children scope. The injection of new A() in S0 will fail with an appropriate error message.
  • Toothpick.inject(new A(), S1) : The injection of new A() in S1 will result in the following :
  • a.foo1 will be an instance of Foo (the instance created during the binding to new Foo();
  • a.foo2 will be the same instance of Foo because we have bound IFoo to a single instance of Foo;
  • a.bar1 and a.bar2 will be both be instances of Bar.class, and will be different from each other.
  • Toothpick.inject(new A(), S2) : The injection of new A() in S2 will result in the exact same thing as in S1 above.
  • a.foo1 will be an instance of Foo (the instance created during the binding to new Foo();
  • a.foo2 will be the same instance of Foo because we have bound IFoo to a single instance of Foo;
  • a.bar1 and a.bar2 will be both be instances of Bar.class, and will be different from each other.

Scoped Bindings

A binding is scoped when we call its method scope(). bind(IFoo.class).to(Foo.class).scope() express more than the simple association IFoo --> Foo. A scoped bindings means :

  • there is an association IFoo --> Foo, in the same way as an unscoped binding does;
  • AND the same instance of Foo is recycled/reused for each injection of IFoo
  • AND the instance of Foo will be created inside the scope. All the dependencies of Foo will have to be found at runtime in the scope where the binding is scoped or in its parent scopes.

A scoped binding is said to be scoped in a given scope. We note scoped bindings IFoo --> (Foo) with the target of the binding in parenthesis.

Clone this wiki locally