-
Notifications
You must be signed in to change notification settings - Fork 115
Factories and Member Injectors
In Toothpick, a Factory<Foo>
is a class that produces instance of Foo
.
As Toothpick does not rely on reflection at all, with the idea in mind that performance is awfully slow on Android, it uses Factory
classes that are generated by an annotation processor.
The factories enable to bypass reflection to access a given constructor.
Toothpick annotation processor will create a factory for all classes that have an injected constructor :
public class Foo {
@Inject Foo() {...}
}
Toothpick will then generate a factory :
//a simplified Factory
public final class Foo$$Factory implements Factory<Foo> {
@Override
public Foo createInstance() {
return new Foo();
}
}
If a class has a @Inject
annotated members such as
class Foo {
@Inject Bar bar;
}
Then Toothpick will create a factory:
- for the class
Foo
(optimistic)
If a class has a @Inject
annotated constructor with parameters such as
class Foo {
@Inject Foo(Bar bar);
}
Then Toothpick will create a factory:
- for the class
Foo
(non optimistic factory, this is the normal case)
If a class is annotated with a scope annotation such as
@Singleton
class Foo {
}
Then Toothpick will create a factory:
- for the class
Foo
(optimistic)
When a class is annotated with a scope annotation, the generated factory will be scoped.
Example:
@ActivitySingleton
class Foo {
@Inject Bar bar;
}
Then Toothpick will create a scoped factory:
//a scoped Factory
public final class Foo$$Factory implements Factory<Foo> {
@Override
public Foo createInstance(Scope scope) {
scope = scope.getParentScope(ActivitySingleton.class);
Bar bar = scope.getInstance(Bar.class);
return new Foo(bar);
}
}
The factory will create all the dependencies of the instance of Foo
inside the scope that is bound to the annotation scope. This ensures that such classes can only be produced within the adequate scope.
As a consequence, it is impossible to use a scope annotated class outside of its scope via Toothpick, no binding can override this scope declaration. The only workaround, which is not a good practice, is to create an instance of this class manually, when possible.
Also, the instance created by a scoped factory will not only be created inside the scope, it will also be reused inside the scope and its children scopes.
Scoped factories are a key component of Scope Resolution. Please refer to this page to understand how scoped factories and scope resolution indeed create a scope verification system that contributes to decrease memory leaks in applications.
In Toothpick, a MemberInjector<Foo>
is a class that injects the fields of the instances of Foo
. It can either assign injected fields or invoke injected methods.
Again, as Toothpick does not rely on reflection at all, with the idea in mind that performance is awfully slow on Android, it uses MemberInjector
classes that are generated by an annotation processor.
The member injectors enable to bypass reflection to access a given field or method.
Toothpick annotation processor will create a Member Injector for all classes that have an injected member (field or method):
public class Foo {
@Inject void m(Bar bar) {...}
}
Toothpick will then generate a member injector :
//a Member Injector
public final class Foo$$MemberInjector implements MemberInjector<Foo> {
@Override
public void inject(Foo foo, Scope s) {
Bar bar = Toothpick.getInstance(Bar.class);
return foo.m(bar);
}
}
Toothpick will always inject all the dependencies (expressed by injected constructors, injected fields or injected methods) of all instances it creates.
To ensure that this rule always applies, factories use member injectors.
Example:
public class Foo {
@Inject Bar bar; //an injected field
@Inject Foo() {...} //an injected constructor
}
Toothpick will then generate a factory :
//a simplified Factory
public final class Foo$$Factory implements Factory<Foo> {
@Override
public Foo createInstance(Scope scope) {
Foo foo = new Foo();
new Foo$$MemberInjector().inject(foo, scope);
return foo;
}
}
This mechanism also enforces that all dependencies of the scope annotated classes are created in the right scope, when we combine this feature and scoped factories.
Inheritance is taken into account by member injectors. A member injector of a class Foo
will use member injectors of the super classes of Foo
if needed.
Example:
class Foo {
@Inject Bar bar;
}
class FooChild extends Foo {
@Inject Qurtz qurtz;
}
In this case
- the member injector of
FooChild
will use the member injector ofFoo
. Any instance ofFooChild
will be injected both by the member injector ofFooChild
AND by the member injector ofFoo
. - this mechanism takes into account the overrides of methods: an injected method that overrides an injected method will only be called once by Toothpick, it is the responsibility of the developer to consider invoking the super version of this method or not.
Once Toothpick will generate the factories and member injectors, it will need to use them at runtime. Using default configurations, Toothpick will use reflection to load factories and member injectors.
Nevertheless, Toothpick is able to work without using reflection at all. In this case, it cannot rely on Class.forName("Foo$$Factory")
to find the factory class. Toothpick will then use registries to locate both the factories and member injectors.