- Applications consist of components and each component has an inner id called Key
Keyconsists of the
Typetype and nullable qualifier
Object(useful when you need different implementations of the same
- 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.
Bindinghas two corresponding attributes:
Bindingis 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
- 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.
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.
Injectorcan enter the scope. This means you create an
Injectorand 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.
This example can show you how scopes works.
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
It’s trivial to manually implement the Module interface, but it’s even easier to extendAbstractModule, which supports @Provides method scanning and the DSL for creating/transforming/generating bindings.