Add value injector

This commit is contained in:
Mike Bierlee 2016-12-09 02:05:03 +01:00
parent c75b025a68
commit d3ed3e91b9
7 changed files with 183 additions and 20 deletions

View file

@ -1,6 +1,7 @@
Poodinis Changelog
==================
**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
**Version 7.0.1**

View file

@ -16,6 +16,7 @@ Features
--------
* 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.
* 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.
* Application contexts: Control the creation of dependencies manually through factory methods.
* Multi-threadable: Dependency containers return the same dependencies across all threads.

View file

@ -20,6 +20,7 @@ module poodinis.autowire;
import poodinis.container;
import poodinis.registration;
import poodinis.factory;
import poodinis.valueinjection;
import std.exception;
import std.stdio;
@ -106,7 +107,7 @@ public void autowire(Type)(shared(DependencyContainer) container, 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)
{
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);
} else static if (__traits(isSame, attribute, Autowire)) {
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.
*

View file

@ -16,3 +16,4 @@ public import poodinis.container;
public import poodinis.registration;
public import poodinis.context;
public import poodinis.factory;
public import poodinis.valueinjection;

View 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);
}

View file

@ -87,6 +87,14 @@ version(unittest) {
public ComponentC[] componentCs;
}
class ValuedClass {
@Value("values.int")
public int intValue;
@Autowire
public ComponentA unrelated;
}
// Test autowiring concrete type to existing instance
unittest {
auto container = new shared DependencyContainer();
@ -229,7 +237,7 @@ version(unittest) {
assert(instance.componentA !is null);
}
// Test autowiring optional depenencies
// Test autowiring optional dependencies
unittest {
auto container = new shared DependencyContainer();
auto instance = new OuttaTime();
@ -240,4 +248,24 @@ version(unittest) {
assert(instance.componentA 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);
}
}

View file

@ -224,6 +224,21 @@ version(unittest) {
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
unittest {
auto container = new shared DependencyContainer();
@ -747,4 +762,40 @@ version(unittest) {
container.register!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);
}
}