Depin Core - Dependency injection framework core module for Ceylon
This module uses standard Ceylon logging defined via module ceylon.logging
. Configuration of logging may be altered using log
value.
Reference to module ceylon.logging
documentation for more information.
Whole concept of this framework, is based on Dependency
and Injection
abstract classes.
Both are tightly coupled together.
The Dependency
class is wrapped Ceylon declaration, with additional information like Identification
,
providing ability to clearly identify what to inject in Injection
.
Dependency
is identified by it's name and declaration open type.
From point of view of Injection
, two declaration (and after provisioning Dependency
) having same name,
(for example from different packages), with different open types are not colliding.
Dependency
has also ability to be resolved.
Resolution process is executed via Dependency.resolve
function, this is done every time dependency has been identified and being injected.
To cache resolution there are Dependency.Decorator
s which can be applied, farther described in this guide.
By default dependency resolution is lazy and not cached in any way.
Injection
is process of resolving dependencies (container and parameters) and calling requested constructor method or getting value.
To use this framework, one need to first provide dependencies, for further injection.
It is done using scanner
object. Scanning is gathering of methods and values declaration annotated with dependency
.
They can be nested in classes and member classes or top level, any formal declaration will be rejected.
The scanner.dependencies
call would provide declarations for further use. This function, takes Scope
s as parameters.
Scope
is range on which scanning would execute. When declaration are already sccaned, they can be used for,Depin
class object creation.
Depin
will convert declarations into Dependency
'ies and provide Depin.inject
method.
Now the injection can happen. Depin.inject
requires Injectable
parameter which is alias for class, function or value model to which injection will happen.
Example:
dependency String topLevelValue="some value"; dependency Integer topLevelFunction(String someString) => someString.size; Integer topLevelInjection(Integer topLevelFunction(String someString), String topLevelValue){ return topLevelFunction(topLevelValue); } shared void run() { value depedencencyDeclarations=scanner.dependencies({`package`}); value result=Depin(depedencencyDeclarations).inject(`topLevelInjection`); assert(topLevelValue.size==result); }
To provide easier interoperation with frameworks where programmer has no control, over creating objects such as Android SDK, Depin.extract
functionality has been introduced. It allows to provide resolved dependencies into the caller. So going with example of Android SDK, in Activity.onCreate
, dependencies can be obtained by using correct naming and typing. Then they can be bounded to late
or variable
fields and used in life-cycle of Activity
. Be aware that Depin
does not provide any ability for disposing of these dependencies. Although this can be achieved using dependency decorators and event handlers, notified through Depin.notify
method. It would vary by use-case, as each framework uses different interface for disposing.
Example:
class UnaccesibleDependencyContainer(){ suppressWarnings("unusedDeclaration") dependency String name="abc"; } late String name; shared void onCreate(){ value dependencies = scanner.dependencies({`package`}); name = Depin(dependencies).extract<String>(`value name`); assert(name=="abc"); print(name); }
Scanner will scan all classes and they members it doesn't matters either they are shared or not. It may be required to pass scopes which will be excluded in scanner.dependencies
.
In this release Depin, does not honor Ceylon encapsulation in any way. Whatever is scanned, can be injected. This will be modified in further release.
For some cases it is required to rename given Dependency
, for such requirements named
annotation has been introduced. It takes String
name as argument.
This hints Depin
that Dependency
created from this named declaration will have name as given in named.name
.
Example:
dependency Integer[] summable =[1,2,3]; class DependencyHolder(named("summable") Integer[] numbers){ named("integerSum") dependency Integer? sum = numbers.reduce((Integer partial, Integer element) => partial+element); } void printInjection(Integer? integerSum){ print("Sum of summable is: ``integerSum else "null"``"); } shared void run(){ Depin{ scanner.dependencies({`package`}); }.inject(`printInjection`); }
It is important to remember that to identify a dependency, it's type, must exactly match with declaration of type in injection.
So in given example sum
is declared with Integer?
type, printInjection
first parameter has exactly the same type! All intersection types, interfaces and unions must match exactly!
Because of https://github.com/eclipse/ceylon/issues/7448 it is not possible to name (using named
annotation) constructor parameters,
for Dependency
containers or injection constructor parameters.
In some cases it is required to declare more than one constructor in a class. Depin
won't be able to gues which constructor to use.
In this case target
can be used. This is applicable for injections and dependencies.
Example:
class TargetedInjection { String constructorName; shared new(){ constructorName="default"; } shared target new targetedConstructor(){ constructorName="targeted"; } shared void printInjection(){ print("Selected construcotr was: ``constructorName``"); } } shared void run(){ Depin{ scanner.dependencies({`package`}); }.inject(`TargetedInjection.printInjection`); }
This framework uses concept of decorators defined via Dependency.Decorator
interface. Each decorator is an annotation,
allowing to change way of dependency resolution. Example usage is to provide ability to define singletons or eager dependency resolvers.
Dependency.Decorator
s can be defined outside of this module, they are recognized during dependency creation from declarations.
This feature in frameworks like Spring is called scopes.
Build in decorators:
singleton
annotation,eager
annotation,fallback
annotation.More information can be found in specific annotation documentation.
Each decorator can be notified, from outside of framework, it needs just to implement Handler
interface.
This feature provides ability to change way decorators works.
For example It allows to free up resources. To notify decorator Depin.notify
method needs to be called.
Collector
class is used for collecting of dependencies with specific open type.
In this case naming doesn't matters.
Depin
will always inject whole known set of dependencies for given type declared in Collector
's Collected
type parameter.
Example:
dependency Integer one=1; dependency Integer two=2; void assertCollectorInjection(Collector<Integer> namingDoesntMatters){ assert(namingDoesntMatters.collected.containsEvery({one,two})); } shared void run(){ Depin{ scanner.dependencies({`package`}); }.inject(`assertCollectorInjection`); } }
Because of Java type-system definition, where generics are not part of the type declaration, Depin
usage can be a bit of pain. For cases, where there are not type parameters dependency injection should function without issues, but whenever generics are in place <out Anything>
, type parameter declaration must be used.
Example:
java class in native jvm module
public class Generic<T>{ private T data; public Generic(T data){ this.data=data; } public T getData(){ return data; } @Override public String toString(){ return data.toString(); } }
dependencies declaration and usage in Ceylon
dependency Generic<out Anything> data= Generic("data"); suppressWarnings("uncheckedTypeArguments") shared Generic<String> injection(Generic<out Anything> data){ assert( is Generic<String> data ); return data; } shared void run(){ value dependencies=scanner.dependencies({`package`}); value result=Depin(dependencies).inject(`injection`); assert(result==data); print(result); }
Packages | |
herd.depin.core | |
herd.depin.core.internal.util |
Dependencies | ||
ceylon.collection | 1.3.3 | |
ceylon.logging | 1.3.3 | |
herd.type.support | 0.2.0 |
herd.depin.core
Aliases | |
Injectable | shared Injectable<Type> Model which can be injected using |
Scope | shared Scope=> ClassDeclaration|FunctionOrValueDeclaration|Package|Module Range of decalarations which can be scanned using |
Annotations | |
dependency | shared DependencyAnnotation dependency() Annotation used for creation of scannable declaration for |
eager | shared EagerDecorator eager() Decoration annotation, used for creating |
fallback | shared FallbackDecorator fallback() Decorator annotation, used for creating a |
named | shared NamedAnnotation named(String name) Annotation allowing to rename dependency, which will be used for injection |
singleton | shared SingletonDecorator singleton() Decorator annotation, used to cache resolution of dependency decorated with. |
target | shared TargetAnnotation target() Annotation, which allows selecting of constructor used for injection using |
DependencyAnnotation | shared final DependencyAnnotation |
EagerDecorator | shared final EagerDecorator |
FallbackDecorator | shared final FallbackDecorator |
NamedAnnotation | shared final NamedAnnotation |
SingletonDecorator | shared final SingletonDecorator |
TargetAnnotation | shared final TargetAnnotation |
Values | |
log | shared Logger log Logger used by |
scanner | shared scanner scanner Scans given scopes and produces declarations to be transformed into |
Interfaces | |
Handler | shared Handler<in Event = Nothing> Event handler for |
Classes | |
Collector | shared final Collector<Collected> Declaration with this type will be treated as one to contain all dependencies of |
Dependency | shared abstract Dependency Defines abstraction over given declaration bound to be injected. Given |
Depin | shared Depin Main entry point for this framework to operate. |
Identification | shared Identification Identifies dependency unequivocaly |
Injection | shared abstract Injection Abstraction over ceylon model providing ablity to define what and how to inject. |
scanner | shared scanner Scans given scopes and produces declarations to be transformed into |