Skip to main content

Basics

Dependency Injection, Keys, Bindings#

  • Applications consist of components and each component has an inner id called Key
  • Key consists of the Type type and nullable qualifier Object (useful when you need different implementations of the same Type):
public class Key<T> {
@NotNull
private final Type type;
@Nullable
private final Object qualifier;
}
  • Application components can require some dependencies in order to be created.
  • Dependency Injection takes care of supplying application components with these required objects.
  • In order to do it, we need to specify what it needs to provide and how to use the provided objects.
  • Therefore, Binding has two corresponding attributes:
    • a set of needed for creation Set<Dependency> dependencies (POJO with Key)
    • BindingCompiler which will compile required instances
public final class Binding<T> {
final Set<Dependency> dependencies;
final BindingCompiler<T> compiler;
}
  • Binding is like a "recipe" of how to create an instance of a component:
    • dependencies show what ingredients should be used
    • compiler knows how to cook them together
  • Now we need something that can use the recipe to cook the component properly, and here comes Injector

Injector#

  • Provides all the required dependencies (injects) for the component recursively traversing the dependencies graph in a postorder way and creates them first.
  • Bindings are by default singletons - if an instance was created once, it won't be recreated from scratch again. If it is needed for other bindings, Injector will take it from cache. You don't need to apply any additional annotations for it.
  • To provide the requested key, Injector recursively creates all of its dependencies and falls back to injector of its parent scope if binding in its scope is not found.

Scopes#

In short - Scope gives us “local singletons” which live as long as the scope itself. ActiveJ Inject scopes are a bit different from other DI libraries:

  • The internal structure of the Injector is a prefix tree and the prefix is a scope.
  • The identifiers (or prefixes) of the tree are simple annotations.
  • Injector can enter the scope. This means you create an Injector and its scope will be set to the one that it's entering.
  • This can be done multiple times, so you can have N injectors in certain scope.
public class Injector {
...
final Trie<Scope, ScopeLocalData> scopeDataTree;
...
public Injector enterScope(Scope scope) {
return new Injector(this, scopeDataTree.get(scope));
}
...
}

This example can show you how scopes works.

Modules#

Dependency graph is hard to create directly, so we provide automatic graph transformation, generation and validation mechanisms with a simple yet powerful DSL.

All of these preprocessing steps are performed in start-up time by compiling Modules

  • Each module exports bindings which are combined with each other. If there are two or more bindings for any single key, they are reduced into one binding with user-provided Multibinder reduce function:

    • This simple solution makes it trivial to implement multibinder sets/maps or any app-specific multibinder
    • If no Multibinder is defined for particular key, exception is thrown
  • If dependency graph has missing dependencies, they are automatically generated with BindingGenerator

    • BindingGenerators are user-defined and exported by Modules
    • There is an implicit DefaultModule with default BindingGenerator, which automatically provides required dependencies by scanning Inject annotations of required classes
    • User-specified modules can also export custom binding generators for special classes
    • You can opt-out of using DefaultModule and its default BindingGenerators
  • All bindings are transformed with user-provided BindingTransformers

    • To intercept/modify/wrap provided instances
    • To intercept/modify/wrap the dependencies of provided instances
  • Multibinders, BindingGenerators and BindingTransformers can be made with clean and extremely simple Java8+ functional DSL

  • Resulting dependency graph is validated - checked for cyclic and missing dependencies, then compiled into a final scope tree and passed to Injector

public interface Module {
Trie<Scope, Map<Key<?>, Set<Binding<?>>>> getBindings();
Map<Integer, Set<BindingTransformer<?>>> getBindingTransformers();
Map<Class<?>, Set<BindingGenerator<?>>> getBindingGenerators();
Map<Key<?>, Multibinder<?>> getMultibinders();
}

It’s trivial to manually implement the Module interface, but it’s even easier to extend

AbstractModule, which supports @Provides method scanning and the DSL for creating/transforming/generating bindings.