mirror of
https://github.com/mbierlee/poodinis.git
synced 2024-11-15 04:04:01 +01:00
Add value injector
This commit is contained in:
parent
c75b025a68
commit
d3ed3e91b9
|
@ -1,6 +1,7 @@
|
||||||
Poodinis Changelog
|
Poodinis Changelog
|
||||||
==================
|
==================
|
||||||
**Version NEXT**
|
**Version NEXT**
|
||||||
|
* ADD value injection. Members with UDA @Value will be attempted to be injected with a value-type. See tutorial and examples for more info.
|
||||||
* FIX nullpointer exception in instance factory when debugging with poodinisVerbose
|
* FIX nullpointer exception in instance factory when debugging with poodinisVerbose
|
||||||
|
|
||||||
**Version 7.0.1**
|
**Version 7.0.1**
|
||||||
|
|
|
@ -16,6 +16,7 @@ Features
|
||||||
--------
|
--------
|
||||||
* Member injection: Injection of dependencies in class members of any visibility (public, private, etc.)
|
* Member injection: Injection of dependencies in class members of any visibility (public, private, etc.)
|
||||||
* Constructor injection: Automatic injection of dependencies in class constructors on creation.
|
* Constructor injection: Automatic injection of dependencies in class constructors on creation.
|
||||||
|
* Value injection: Value-types such as primitives or structs can be injected using custom value injectors.
|
||||||
* Type qualifiers: Inject concrete types into members defined only by abstract types.
|
* Type qualifiers: Inject concrete types into members defined only by abstract types.
|
||||||
* Application contexts: Control the creation of dependencies manually through factory methods.
|
* Application contexts: Control the creation of dependencies manually through factory methods.
|
||||||
* Multi-threadable: Dependency containers return the same dependencies across all threads.
|
* Multi-threadable: Dependency containers return the same dependencies across all threads.
|
||||||
|
|
|
@ -20,6 +20,7 @@ module poodinis.autowire;
|
||||||
import poodinis.container;
|
import poodinis.container;
|
||||||
import poodinis.registration;
|
import poodinis.registration;
|
||||||
import poodinis.factory;
|
import poodinis.factory;
|
||||||
|
import poodinis.valueinjection;
|
||||||
|
|
||||||
import std.exception;
|
import std.exception;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
@ -106,7 +107,7 @@ public void autowire(Type)(shared(DependencyContainer) container, Type instance)
|
||||||
printDebugAutowiredInstance(typeid(Type), &instance);
|
printDebugAutowiredInstance(typeid(Type), &instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: recurse into base class if there are more between Type and Object in the hirarchy
|
// Recurse into base class if there are more between Type and Object in the hierarchy
|
||||||
static if(BaseClassesTuple!Type.length > 1)
|
static if(BaseClassesTuple!Type.length > 1)
|
||||||
{
|
{
|
||||||
autowire!(BaseClassesTuple!Type[0])(container, instance);
|
autowire!(BaseClassesTuple!Type[0])(container, instance);
|
||||||
|
@ -131,6 +132,9 @@ private void autowireMember(string member, size_t memberIndex, Type)(shared(Depe
|
||||||
injectInstance!(member, memberIndex, typeof(attribute.qualifier))(container, instance);
|
injectInstance!(member, memberIndex, typeof(attribute.qualifier))(container, instance);
|
||||||
} else static if (__traits(isSame, attribute, Autowire)) {
|
} else static if (__traits(isSame, attribute, Autowire)) {
|
||||||
injectInstance!(member, memberIndex, UseMemberType)(container, instance);
|
injectInstance!(member, memberIndex, UseMemberType)(container, instance);
|
||||||
|
} else static if (is(typeof(attribute) == Value)) {
|
||||||
|
enum key = attribute.key;
|
||||||
|
injectValue!(member, memberIndex, key)(container, instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,6 +204,19 @@ private QualifierType createOrResolveInstance(MemberType, QualifierType, bool cr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void injectValue(string member, size_t memberIndex, string key, Type)(shared(DependencyContainer) container, Type instance) {
|
||||||
|
alias MemberType = typeof(Type.tupleof[memberIndex]);
|
||||||
|
auto injector = container.resolve!(ValueInjector!MemberType);
|
||||||
|
instance.tupleof[memberIndex] = injector.get(key);
|
||||||
|
debug(poodinisVerbose) {
|
||||||
|
printDebugValueInjection(typeid(Type), &instance, member, typeid(MemberType), key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printDebugValueInjection(TypeInfo instanceType, void* instanceAddress, string member, TypeInfo valueType, string key) {
|
||||||
|
writeln(format("DEBUG: Injected value with key '%s' of type %s into [%s@%s].%s", key, valueType, instanceType, instanceAddress, member));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Autowire the given instance using the globally available dependency container.
|
* Autowire the given instance using the globally available dependency container.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,3 +16,4 @@ public import poodinis.container;
|
||||||
public import poodinis.registration;
|
public import poodinis.registration;
|
||||||
public import poodinis.context;
|
public import poodinis.context;
|
||||||
public import poodinis.factory;
|
public import poodinis.factory;
|
||||||
|
public import poodinis.valueinjection;
|
||||||
|
|
64
source/poodinis/valueinjection.d
Normal file
64
source/poodinis/valueinjection.d
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* This module contains facilities to support value injection. Actual injection is done by the
|
||||||
|
* autowiring mechanism.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Mike Bierlee, m.bierlee@lostmoment.com
|
||||||
|
* Copyright: 2014-2016 Mike Bierlee
|
||||||
|
* License:
|
||||||
|
* This software is licensed under the terms of the MIT license.
|
||||||
|
* The full terms of the license can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
module poodinis.valueinjection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UDA used for marking class members which should be value-injected.
|
||||||
|
*
|
||||||
|
* A key must be supplied, which can be in any format depending on how
|
||||||
|
* a value injector reads it.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* ---
|
||||||
|
* class MyClass {
|
||||||
|
* @Value("general.importantNumber")
|
||||||
|
* private int number;
|
||||||
|
* }
|
||||||
|
* ---
|
||||||
|
*/
|
||||||
|
struct Value {
|
||||||
|
string key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface which should be implemented by value injectors.
|
||||||
|
*
|
||||||
|
* Each value injector injects one specific type. The type can be any primitive
|
||||||
|
* type or that of a struct. While class types are also supported, value injectors
|
||||||
|
* are not intended for them.
|
||||||
|
*
|
||||||
|
* Note that value injectors are also autowired before being used. Value injectors should
|
||||||
|
* not contain dependencies on classes which require value injection. Neither should a
|
||||||
|
* value injector have members which are to be value-injected.
|
||||||
|
*
|
||||||
|
* Value injection is not supported for constructor injection.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* ---
|
||||||
|
* class MyIntInjector : ValueInjector!int {
|
||||||
|
* public override int get(string key) { ... }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // In order to make the container use your injector, register it by interface:
|
||||||
|
* container.register!(ValueInjector!int, MyIntInjector);
|
||||||
|
* ---
|
||||||
|
*/
|
||||||
|
interface ValueInjector(Type) {
|
||||||
|
/**
|
||||||
|
* Get a value from the injector by key.
|
||||||
|
*
|
||||||
|
* The key can have any format. Generally you are encouraged
|
||||||
|
* to accept a dot separated path, for example: server.http.port
|
||||||
|
*/
|
||||||
|
Type get(string key);
|
||||||
|
}
|
||||||
|
|
|
@ -87,6 +87,14 @@ version(unittest) {
|
||||||
public ComponentC[] componentCs;
|
public ComponentC[] componentCs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ValuedClass {
|
||||||
|
@Value("values.int")
|
||||||
|
public int intValue;
|
||||||
|
|
||||||
|
@Autowire
|
||||||
|
public ComponentA unrelated;
|
||||||
|
}
|
||||||
|
|
||||||
// Test autowiring concrete type to existing instance
|
// Test autowiring concrete type to existing instance
|
||||||
unittest {
|
unittest {
|
||||||
auto container = new shared DependencyContainer();
|
auto container = new shared DependencyContainer();
|
||||||
|
@ -229,7 +237,7 @@ version(unittest) {
|
||||||
assert(instance.componentA !is null);
|
assert(instance.componentA !is null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test autowiring optional depenencies
|
// Test autowiring optional dependencies
|
||||||
unittest {
|
unittest {
|
||||||
auto container = new shared DependencyContainer();
|
auto container = new shared DependencyContainer();
|
||||||
auto instance = new OuttaTime();
|
auto instance = new OuttaTime();
|
||||||
|
@ -240,4 +248,24 @@ version(unittest) {
|
||||||
assert(instance.componentA is null);
|
assert(instance.componentA is null);
|
||||||
assert(instance.componentCs is null);
|
assert(instance.componentCs is null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test autowiring class using value injection
|
||||||
|
unittest {
|
||||||
|
auto container = new shared DependencyContainer();
|
||||||
|
class TestInjector : ValueInjector!int {
|
||||||
|
public override int get(string key) {
|
||||||
|
assert(key == "values.int");
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.register!(ValueInjector!int, TestInjector);
|
||||||
|
container.register!ComponentA;
|
||||||
|
auto instance = new ValuedClass();
|
||||||
|
|
||||||
|
container.autowire(instance);
|
||||||
|
|
||||||
|
assert(instance.intValue == 8);
|
||||||
|
assert(instance.unrelated !is null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,6 +224,21 @@ version(unittest) {
|
||||||
this(Ola ola) {}
|
this(Ola ola) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Thing {
|
||||||
|
int x;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyConfig {
|
||||||
|
@Value("conf.stuffs")
|
||||||
|
int stuffs;
|
||||||
|
|
||||||
|
@Value("conf.name")
|
||||||
|
string name;
|
||||||
|
|
||||||
|
@Value("conf.thing")
|
||||||
|
Thing thing;
|
||||||
|
}
|
||||||
|
|
||||||
// Test register concrete type
|
// Test register concrete type
|
||||||
unittest {
|
unittest {
|
||||||
auto container = new shared DependencyContainer();
|
auto container = new shared DependencyContainer();
|
||||||
|
@ -747,4 +762,40 @@ version(unittest) {
|
||||||
container.register!Hello;
|
container.register!Hello;
|
||||||
container.resolve!Hello;
|
container.resolve!Hello;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test injection of values
|
||||||
|
unittest {
|
||||||
|
auto container = new shared DependencyContainer();
|
||||||
|
container.register!MyConfig;
|
||||||
|
|
||||||
|
class IntInjector : ValueInjector!int {
|
||||||
|
public override int get(string key) {
|
||||||
|
assert(key == "conf.stuffs");
|
||||||
|
return 364;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringInjector : ValueInjector!string {
|
||||||
|
public override string get(string key) {
|
||||||
|
assert(key == "conf.name");
|
||||||
|
return "Le Chef";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThingInjector : ValueInjector!Thing {
|
||||||
|
public override Thing get(string key) {
|
||||||
|
assert(key == "conf.thing");
|
||||||
|
return Thing(8899);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.register!(ValueInjector!int, IntInjector);
|
||||||
|
container.register!(ValueInjector!string, StringInjector);
|
||||||
|
container.register!(ValueInjector!Thing, ThingInjector);
|
||||||
|
|
||||||
|
auto instance = container.resolve!MyConfig;
|
||||||
|
assert(instance.stuffs == 364);
|
||||||
|
assert(instance.name == "Le Chef");
|
||||||
|
assert(instance.thing.x == 8899);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue