Add constructor injection

This commit is contained in:
Mike Bierlee 2016-08-22 23:10:29 +02:00
parent 81c6faed16
commit ab765e0092
4 changed files with 231 additions and 2 deletions

View file

@ -147,7 +147,8 @@ synchronized class DependencyContainer {
return existingRegistration; 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(); newRegistration.singleInstance();
static if (!is(SuperType == ConcreteType)) { static if (!is(SuperType == ConcreteType)) {

View file

@ -11,8 +11,12 @@
module poodinis.factory; module poodinis.factory;
import poodinis.container;
import std.typecons; import std.typecons;
import std.exception; import std.exception;
import std.traits;
import std.meta;
debug { debug {
import std.string; import std.string;
@ -39,6 +43,10 @@ class InstanceFactory {
private Object instance = null; private Object instance = null;
private InstanceFactoryParameters _factoryParameters; private InstanceFactoryParameters _factoryParameters;
this() {
factoryParameters = InstanceFactoryParameters();
}
public @property void factoryParameters(InstanceFactoryParameters factoryParameters) { public @property void factoryParameters(InstanceFactoryParameters factoryParameters) {
if (factoryParameters.factoryMethod is null) { if (factoryParameters.factoryMethod is null) {
factoryParameters.factoryMethod = &this.createInstance; factoryParameters.factoryMethod = &this.createInstance;
@ -73,8 +81,65 @@ class InstanceFactory {
return instance; return instance;
} }
private Object createInstance() { protected Object createInstance() {
enforce!InstanceCreationException(_factoryParameters.instanceType, "Instance type is not defined, cannot create instance without knowing its type."); enforce!InstanceCreationException(_factoryParameters.instanceType, "Instance type is not defined, cannot create instance without knowing its type.");
return _factoryParameters.instanceType.create(); 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;
}
}

View file

@ -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 // Test register concrete type
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
@ -651,4 +662,18 @@ version(unittest) {
auto instances = container.resolveAll!TestInterface(ResolveOption.noResolveException); auto instances = container.resolveAll!TestInterface(ResolveOption.noResolveException);
assert(instances.length == 0); 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);
}
} }

View file

@ -7,6 +7,8 @@
import poodinis; import poodinis;
import std.exception;
version(unittest) { version(unittest) {
interface TestInterface {} interface TestInterface {}
@ -15,6 +17,68 @@ version(unittest) {
public string someContent = ""; 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 // Test instance factory with singletons
unittest { unittest {
auto factory = new InstanceFactory(); 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 !is null, "No instance was created by factory or could not be cast to expected type");
assert(instance.someContent == "Ducks!"); 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());
}
} }