Prevent creation of circular dependencies in constructors

This commit is contained in:
Mike Bierlee 2016-09-03 16:54:43 +02:00
parent 96f52226b4
commit 95e30477c6
3 changed files with 22 additions and 1 deletions

View file

@ -137,7 +137,7 @@ Constructor injection has the advantage of not having to import Poodinis through
Circular dependencies Circular dependencies
--------------------- ---------------------
Poodinis can autowire circular dependencies when they are registered with `singleInstance` or `existingInstance` registration scopes. Circular dependencies in registrations with `newInstance` scopes will not be autowired, as this would cause an endless loop. Poodinis can autowire circular dependencies when they are registered with `singleInstance` or `existingInstance` registration scopes. Circular dependencies in registrations with `newInstance` scopes will not be autowired, as this would cause an endless loop. Circular dependencies are only supported when autowiring members through the `@Autowire` UDA; circular dependencies in constructors are not supported and will result in an `InstanceCreationException`.
Registering and resolving using qualifiers Registering and resolving using qualifiers
------------------------------------------ ------------------------------------------

View file

@ -89,6 +89,7 @@ class InstanceFactory {
class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory { class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory {
private shared DependencyContainer container; private shared DependencyContainer container;
private bool isBeingInjected = false;
this(shared DependencyContainer container) { this(shared DependencyContainer container) {
this.container = container; this.container = container;
@ -120,15 +121,18 @@ class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory {
protected override Object createInstance() { protected override Object createInstance() {
enforce!InstanceCreationException(container, "A dependency container is not defined. Cannot perform constructor injection without one."); enforce!InstanceCreationException(container, "A dependency container is not defined. Cannot perform constructor injection without one.");
enforce!InstanceCreationException(!isBeingInjected, format("%s is already being created and injected; possible circular dependencies in constructors?", InstanceType.stringof));
Object instance = null; Object instance = null;
static if (__traits(compiles, __traits(getOverloads, InstanceType, `__ctor`))) { static if (__traits(compiles, __traits(getOverloads, InstanceType, `__ctor`))) {
foreach(ctor ; __traits(getOverloads, InstanceType, `__ctor`)) { foreach(ctor ; __traits(getOverloads, InstanceType, `__ctor`)) {
static if (parametersAreValid!(Parameters!ctor)) { static if (parametersAreValid!(Parameters!ctor)) {
isBeingInjected = true;
mixin(` mixin(`
import ` ~ moduleName!InstanceType ~ `; import ` ~ moduleName!InstanceType ~ `;
instance = new ` ~ fullyQualifiedName!InstanceType ~ `(` ~ createArgumentList!(Parameters!ctor) ~ `); instance = new ` ~ fullyQualifiedName!InstanceType ~ `(` ~ createArgumentList!(Parameters!ctor) ~ `);
`); `);
isBeingInjected = false;
break; break;
} }
} }

View file

@ -199,6 +199,14 @@ version(unittest) {
} }
} }
class Pot {
this(Kettle kettle) {}
}
class Kettle {
this(Pot pot) {}
}
// Test register concrete type // Test register concrete type
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
@ -695,4 +703,13 @@ version(unittest) {
assert(instance !is null); assert(instance !is null);
assert(instance.color is container.resolve!Blue); assert(instance.color is container.resolve!Blue);
} }
// Test prevention of circular dependencies during constructor injection
unittest {
auto container = new shared DependencyContainer();
container.register!Pot;
container.register!Kettle;
assertThrown!InstanceCreationException(container.resolve!Pot);
}
} }