diff --git a/TUTORIAL.md b/TUTORIAL.md index 2c3834e..c2d5fe4 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -137,7 +137,7 @@ Constructor injection has the advantage of not having to import Poodinis through 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 ------------------------------------------ diff --git a/source/poodinis/factory.d b/source/poodinis/factory.d index 822019f..fe1f827 100644 --- a/source/poodinis/factory.d +++ b/source/poodinis/factory.d @@ -89,6 +89,7 @@ class InstanceFactory { class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory { private shared DependencyContainer container; + private bool isBeingInjected = false; this(shared DependencyContainer container) { this.container = container; @@ -120,15 +121,18 @@ class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory { protected override Object createInstance() { 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; static if (__traits(compiles, __traits(getOverloads, InstanceType, `__ctor`))) { foreach(ctor ; __traits(getOverloads, InstanceType, `__ctor`)) { static if (parametersAreValid!(Parameters!ctor)) { + isBeingInjected = true; mixin(` import ` ~ moduleName!InstanceType ~ `; instance = new ` ~ fullyQualifiedName!InstanceType ~ `(` ~ createArgumentList!(Parameters!ctor) ~ `); `); + isBeingInjected = false; break; } } diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 31a2383..f092960 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -199,6 +199,14 @@ version(unittest) { } } + class Pot { + this(Kettle kettle) {} + } + + class Kettle { + this(Pot pot) {} + } + // Test register concrete type unittest { auto container = new shared DependencyContainer(); @@ -695,4 +703,13 @@ version(unittest) { assert(instance !is null); 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); + } }