From ab765e009283c0c24b5fd3298a091fc675187cf9 Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Mon, 22 Aug 2016 23:10:29 +0200 Subject: [PATCH] Add constructor injection --- source/poodinis/container.d | 3 +- source/poodinis/factory.d | 67 ++++++++++++++++- test/poodinis/containertest.d | 25 ++++++ test/poodinis/factorytest.d | 138 ++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 2 deletions(-) diff --git a/source/poodinis/container.d b/source/poodinis/container.d index 94fca9f..7d6229d 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -147,7 +147,8 @@ synchronized class DependencyContainer { return existingRegistration; } - auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, new InstanceFactory(), this); + auto instanceFactory = new ConstructorInjectingInstanceFactory!ConcreteType(this); + auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, instanceFactory, this); newRegistration.singleInstance(); static if (!is(SuperType == ConcreteType)) { diff --git a/source/poodinis/factory.d b/source/poodinis/factory.d index a39eb3f..822019f 100644 --- a/source/poodinis/factory.d +++ b/source/poodinis/factory.d @@ -11,8 +11,12 @@ module poodinis.factory; +import poodinis.container; + import std.typecons; import std.exception; +import std.traits; +import std.meta; debug { import std.string; @@ -39,6 +43,10 @@ class InstanceFactory { private Object instance = null; private InstanceFactoryParameters _factoryParameters; + this() { + factoryParameters = InstanceFactoryParameters(); + } + public @property void factoryParameters(InstanceFactoryParameters factoryParameters) { if (factoryParameters.factoryMethod is null) { factoryParameters.factoryMethod = &this.createInstance; @@ -73,8 +81,65 @@ class InstanceFactory { return instance; } - private Object createInstance() { + protected Object createInstance() { enforce!InstanceCreationException(_factoryParameters.instanceType, "Instance type is not defined, cannot create instance without knowing its type."); return _factoryParameters.instanceType.create(); } } + +class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory { + private shared DependencyContainer container; + + this(shared DependencyContainer container) { + this.container = container; + } + + private static string createArgumentList(Params...)() { + string argumentList = ""; + foreach(param; Params) { + if (argumentList.length > 0) { + argumentList ~= ","; + } + + argumentList ~= "container.resolve!" ~ param.stringof; + } + return argumentList; + } + + private static bool parametersAreValid(Params...)() { + bool isValid = true; + foreach(param; Params) { + if (isBuiltinType!param) { + isValid = false; + break; + } + } + + return isValid; + } + + protected override Object createInstance() { + enforce!InstanceCreationException(container, "A dependency container is not defined. Cannot perform constructor injection without one."); + + Object instance = null; + static if (__traits(compiles, __traits(getOverloads, InstanceType, `__ctor`))) { + foreach(ctor ; __traits(getOverloads, InstanceType, `__ctor`)) { + static if (parametersAreValid!(Parameters!ctor)) { + mixin(` + import ` ~ moduleName!InstanceType ~ `; + instance = new ` ~ fullyQualifiedName!InstanceType ~ `(` ~ createArgumentList!(Parameters!ctor) ~ `); + `); + break; + } + } + } + + if (instance is null) { + instance = typeid(InstanceType).create(); + } + + enforce!InstanceCreationException(instance !is null, "Unable to create instance of type" ~ InstanceType.stringof ~ ", does it have injectable constructors?"); + + return instance; + } +} diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 92bfc27..5c57f7a 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -180,6 +180,17 @@ version(unittest) { } } + class Cocktail { + @Autowire + public Moolah moolah; + + public Red red; + + this(Red red) { + this.red = red; + } + } + // Test register concrete type unittest { auto container = new shared DependencyContainer(); @@ -651,4 +662,18 @@ version(unittest) { auto instances = container.resolveAll!TestInterface(ResolveOption.noResolveException); assert(instances.length == 0); } + + // Test autowired, constructor injected class + unittest { + auto container = new shared DependencyContainer(); + container.register!Red; + container.register!Moolah; + container.register!Cocktail; + + auto instance = container.resolve!Cocktail; + + assert(instance !is null); + assert(instance.moolah is container.resolve!Moolah); + assert(instance.red is container.resolve!Red); + } } diff --git a/test/poodinis/factorytest.d b/test/poodinis/factorytest.d index 1bede46..e6651a8 100644 --- a/test/poodinis/factorytest.d +++ b/test/poodinis/factorytest.d @@ -7,6 +7,8 @@ import poodinis; +import std.exception; + version(unittest) { interface TestInterface {} @@ -15,6 +17,68 @@ version(unittest) { public string someContent = ""; } + class SomeOtherClassThen { + } + + class ClassWithConstructor { + public TestImplementation testImplementation; + + this(TestImplementation testImplementation) { + this.testImplementation = testImplementation; + } + } + + class ClassWithMultipleConstructors { + public SomeOtherClassThen someOtherClassThen; + public TestImplementation testImplementation; + + this(SomeOtherClassThen someOtherClassThen) { + this.someOtherClassThen = someOtherClassThen; + } + + this(SomeOtherClassThen someOtherClassThen, TestImplementation testImplementation) { + this.someOtherClassThen = someOtherClassThen; + this.testImplementation = testImplementation; + } + } + + class ClassWithConstructorWithMultipleParameters { + public SomeOtherClassThen someOtherClassThen; + public TestImplementation testImplementation; + + this(SomeOtherClassThen someOtherClassThen, TestImplementation testImplementation) { + this.someOtherClassThen = someOtherClassThen; + this.testImplementation = testImplementation; + } + } + + class ClassWithPrimitiveConstructor { + public SomeOtherClassThen someOtherClassThen; + + this(string willNotBePicked) { + } + + this(SomeOtherClassThen someOtherClassThen) { + this.someOtherClassThen = someOtherClassThen; + } + } + + class ClassWithEmptyConstructor { + public SomeOtherClassThen someOtherClassThen; + + this() { + } + + this(SomeOtherClassThen someOtherClassThen) { + this.someOtherClassThen = someOtherClassThen; + } + } + + class ClassWithNonInjectableConstructor { + this(string myName) { + } + } + // Test instance factory with singletons unittest { auto factory = new InstanceFactory(); @@ -74,4 +138,78 @@ version(unittest) { assert(instance !is null, "No instance was created by factory or could not be cast to expected type"); assert(instance.someContent == "Ducks!"); } + + // Test injecting constructor of class + unittest { + auto container = new shared DependencyContainer(); + container.register!TestImplementation; + + auto factory = new ConstructorInjectingInstanceFactory!ClassWithConstructor(container); + auto instance = cast(ClassWithConstructor) factory.getInstance(); + + assert(instance !is null); + assert(instance.testImplementation is container.resolve!TestImplementation); + } + + // Test injecting constructor of class with multiple constructors injects the first candidate + unittest { + auto container = new shared DependencyContainer(); + container.register!SomeOtherClassThen; + container.register!TestImplementation; + + auto factory = new ConstructorInjectingInstanceFactory!ClassWithMultipleConstructors(container); + auto instance = cast(ClassWithMultipleConstructors) factory.getInstance(); + + assert(instance !is null); + assert(instance.someOtherClassThen is container.resolve!SomeOtherClassThen); + assert(instance.testImplementation is null); + } + + // Test injecting constructor of class with multiple constructor parameters + unittest { + auto container = new shared DependencyContainer(); + container.register!SomeOtherClassThen; + container.register!TestImplementation; + + auto factory = new ConstructorInjectingInstanceFactory!ClassWithConstructorWithMultipleParameters(container); + auto instance = cast(ClassWithConstructorWithMultipleParameters) factory.getInstance(); + + assert(instance !is null); + assert(instance.someOtherClassThen is container.resolve!SomeOtherClassThen); + assert(instance.testImplementation is container.resolve!TestImplementation); + } + + // Test injecting constructor of class with primitive constructor parameters + unittest { + auto container = new shared DependencyContainer(); + container.register!SomeOtherClassThen; + + auto factory = new ConstructorInjectingInstanceFactory!ClassWithPrimitiveConstructor(container); + auto instance = cast(ClassWithPrimitiveConstructor) factory.getInstance(); + + assert(instance !is null); + assert(instance.someOtherClassThen is container.resolve!SomeOtherClassThen); + } + + + // Test injecting constructor of class with empty constructor will skip injection + unittest { + auto container = new shared DependencyContainer(); + + auto factory = new ConstructorInjectingInstanceFactory!ClassWithEmptyConstructor(container); + auto instance = cast(ClassWithEmptyConstructor) factory.getInstance(); + + assert(instance !is null); + assert(instance.someOtherClassThen is null); + } + + // Test injecting constructor of class with no candidates fails + unittest { + auto container = new shared DependencyContainer(); + + auto factory = new ConstructorInjectingInstanceFactory!ClassWithNonInjectableConstructor(container); + + assertThrown!InstanceCreationException(factory.getInstance()); + } + }