From d3ed3e91b9c8092c6561b46b09b5d547ca195b02 Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Fri, 9 Dec 2016 02:05:03 +0100 Subject: [PATCH] Add value injector --- CHANGES.md | 1 + README.md | 1 + source/poodinis/autowire.d | 19 +++++++++- source/poodinis/package.d | 37 +++++++++--------- source/poodinis/valueinjection.d | 64 ++++++++++++++++++++++++++++++++ test/poodinis/autowiretest.d | 30 ++++++++++++++- test/poodinis/containertest.d | 51 +++++++++++++++++++++++++ 7 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 source/poodinis/valueinjection.d diff --git a/CHANGES.md b/CHANGES.md index f9ec50e..bab0841 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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** diff --git a/README.md b/README.md index bad51bc..94ae1f9 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index f21c1a3..941bf8d 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -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. * diff --git a/source/poodinis/package.d b/source/poodinis/package.d index 3cffcbe..0ef133b 100644 --- a/source/poodinis/package.d +++ b/source/poodinis/package.d @@ -1,18 +1,19 @@ -/** - * Package module for the Poodinis Dependency Injection framework. - * - * 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; - -public import poodinis.autowire; -public import poodinis.container; -public import poodinis.registration; -public import poodinis.context; -public import poodinis.factory; +/** + * Package module for the Poodinis Dependency Injection framework. + * + * 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; + +public import poodinis.autowire; +public import poodinis.container; +public import poodinis.registration; +public import poodinis.context; +public import poodinis.factory; +public import poodinis.valueinjection; diff --git a/source/poodinis/valueinjection.d b/source/poodinis/valueinjection.d new file mode 100644 index 0000000..3a28579 --- /dev/null +++ b/source/poodinis/valueinjection.d @@ -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); +} + diff --git a/test/poodinis/autowiretest.d b/test/poodinis/autowiretest.d index dd9720f..cbfe613 100644 --- a/test/poodinis/autowiretest.d +++ b/test/poodinis/autowiretest.d @@ -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); + } } diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index f9147b9..2e351d1 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -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); + } }