diff --git a/CHANGES.md b/CHANGES.md index b139288..6677926 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ Poodinis Changelog ================== +**Version NEXT** +* ADD ability to mark autowire dependencies as optional. When you use UDA @OptionalDependency, a type which fails to autowire will remain null +(or an empty array). No ResolveException is thrown. + **Version 6.1.0** * ADD setting persistent registration and resolve options * DEPRECATE DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION, use doNotAddConcreteTypeRegistration instead diff --git a/TUTORIAL.md b/TUTORIAL.md index c735eff..b15140c 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -94,6 +94,17 @@ assert(instance.dependency !is null); ``` If an interface is to be autowired, you must register a concrete class by interface. Any class registered by concrete type can only be injected when a dependency on a concrete type is autowired. +Using the UDA `OptionalDependency` you can mark an autowired member as being optional. When a member is optional, no ResolveException will be thrown when +the type of the member is not registered and `ResolveOption.registerBeforeResolving` is not set on the container. The member will remain null or an empty array in +case of array dependencies. +```d +class ExampleClass { + @Autowire + @OptionalDependency + private AnotherExampleClass dependency; +} +``` + 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. diff --git a/example/annotations/app.d b/example/annotations/app.d index 804d6fe..b3a49b5 100644 --- a/example/annotations/app.d +++ b/example/annotations/app.d @@ -12,6 +12,12 @@ import std.digest.md; import std.stdio; import std.conv; +class SecurityAuditor { + public void submitAudit() { + writeln("Hmmmyes I have received your audit. It is.... adequate."); + } +} + class SuperSecurityDevice { private int seed; @@ -32,6 +38,18 @@ class SecurityManager { @Autowire @AssignNewInstance private SuperSecurityDevice levelTwoSecurity; + + @Autowire + @OptionalDependency + private SecurityAuditor auditor; + + public void doAudit() { + if (auditor !is null) { + auditor.submitAudit(); + } else { + writeln("I uh, will skip the audit for now..."); + } + } } void main() { @@ -45,6 +63,10 @@ void main() { writeln("Password for user two: " ~ manager.levelTwoSecurity.getPassword()); if (manager.levelOneSecurity is manager.levelTwoSecurity) { - writeln("SECURITY BREACH!!!!!"); + writeln("SECURITY BREACH!!!!!"); // Should not be printed since levelTwoSecurity is a new instance. + } else { + writeln("Security okay!"); } + + manager.doAudit(); // Will not cause the SecurityAuditor to print, since we didn't register a SecurityAuditor. } diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index a57f595..971cc01 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -65,6 +65,14 @@ struct Autowire(QualifierType = UseMemberType) { QualifierType qualifier; }; + +/** + * UDA for marking autowired dependencies optional. + * Optional dependencies will not lead to a resolveException when there is no type registered for them. + * The member will remain null. + */ +struct OptionalDependency {}; + /** * UDA for annotating class members to be autowired with a new instance regardless of their registration scope. * @@ -127,10 +135,15 @@ private void autowireMember(string member, size_t memberIndex, Type)(shared(Depe alias MemberType = typeof(Type.tupleof[memberIndex]); enum assignNewInstance = hasUDA!(Type.tupleof[memberIndex], AssignNewInstance); + enum isOptional = hasUDA!(Type.tupleof[memberIndex], OptionalDependency); static if (isDynamicArray!MemberType) { alias MemberElementType = ElementType!MemberType; - auto instances = container.resolveAll!MemberElementType; + static if (isOptional) { + auto instances = container.resolveAll!MemberElementType([ResolveOption.noResolveException]); + } else { + auto instances = container.resolveAll!MemberElementType; + } instance.tupleof[memberIndex] = instances; debug(poodinisVerbose) { printDebugAutowiringArray(typeid(MemberElementType), typeid(Type), &instance, member); @@ -143,12 +156,12 @@ private void autowireMember(string member, size_t memberIndex, Type)(shared(Depe MemberType qualifiedInstance; static if (is(autowireAttribute == Autowire!T, T) && !is(autowireAttribute.qualifier == UseMemberType)) { alias QualifierType = typeof(autowireAttribute.qualifier); - qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, assignNewInstance)(container); + qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, assignNewInstance, isOptional)(container); debug(poodinisVerbose) { qualifiedInstanceType = typeid(QualifierType); } } else { - qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, assignNewInstance)(container); + qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, assignNewInstance, isOptional)(container); } instance.tupleof[memberIndex] = qualifiedInstance; @@ -164,12 +177,16 @@ private void autowireMember(string member, size_t memberIndex, Type)(shared(Depe } } -private QualifierType createOrResolveInstance(MemberType, QualifierType, bool createNew)(shared(DependencyContainer) container) { +private QualifierType createOrResolveInstance(MemberType, QualifierType, bool createNew, bool isOptional)(shared(DependencyContainer) container) { static if (createNew) { auto instanceFactory = new InstanceFactory(typeid(MemberType), CreatesSingleton.no, null); return cast(MemberType) instanceFactory.getInstance(); } else { - return container.resolve!(MemberType, QualifierType); + static if (isOptional) { + return container.resolve!(MemberType, QualifierType)([ResolveOption.noResolveException]); + } else { + return container.resolve!(MemberType, QualifierType); + } } } diff --git a/test/poodinis/autowiretest.d b/test/poodinis/autowiretest.d index 4a2fd1c..432deb4 100644 --- a/test/poodinis/autowiretest.d +++ b/test/poodinis/autowiretest.d @@ -1,217 +1,243 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2016 Mike Bierlee - * This software is licensed under the terms of the MIT license. - * The full terms of the license can be found in the LICENSE file. - */ - -import poodinis; - -import std.exception; - -version(unittest) { - class ComponentA {} - - class ComponentB { - public @Autowire ComponentA componentA; - } - - interface InterfaceA {} - - class ComponentC : InterfaceA {} - - class ComponentD { - public @Autowire InterfaceA componentC = null; - private @Autowire InterfaceA privateComponentC = null; - } - - class DummyAttribute{}; - - class ComponentE { - @DummyAttribute - public ComponentC componentC; - } - - class ComponentDeclarationCocktail { - alias noomer = int; - - @Autowire - public ComponentA componentA; - - public void doesNothing() { - } - - ~this(){ - } - } - - class ComponentX : InterfaceA {} - - class ComponentZ : ComponentB { - } - - class MonkeyShine { - @Autowire!ComponentX - public InterfaceA component; - } - - class BootstrapBootstrap { - @Autowire!ComponentX - public InterfaceA componentX; - - @Autowire!ComponentC - public InterfaceA componentC; - } - - class LordOfTheComponents { - @Autowire - public InterfaceA[] components; - } - class ComponentCharlie { - @Autowire - @AssignNewInstance - public ComponentA componentA; - } - - // Test autowiring concrete type to existing instance - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - auto componentB = new ComponentB(); - container.autowire(componentB); - assert(componentB !is null, "Autowirable dependency failed to autowire"); - } - - // Test autowiring interface type to existing instance - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - auto componentD = new ComponentD(); - container.autowire(componentD); - assert(componentD.componentC !is null, "Autowirable dependency failed to autowire"); - } - - // Test autowiring private members - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - auto componentD = new ComponentD(); - container.autowire(componentD); - assert(componentD.privateComponentC is componentD.componentC, "Autowire private dependency failed"); - } - - // Test autowiring will only happen once - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC).newInstance(); - auto componentD = new ComponentD(); - container.autowire(componentD); - auto expectedComponent = componentD.componentC; - container.autowire(componentD); - auto actualComponent = componentD.componentC; - assert(expectedComponent is actualComponent, "Autowiring the second time wired a different instance"); - } - - // Test autowiring unregistered type - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto componentD = new ComponentD(); - assertThrown!(ResolveException)(container.autowire(componentD), "Autowiring unregistered type should throw ResolveException"); - } - - // Test autowiring member with non-autowire attribute does not autowire - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto componentE = new ComponentE(); - container.autowire(componentE); - assert(componentE.componentC is null, "Autowiring should not occur for members with attributes other than @Autowire"); - } - - // Test autowire class with alias declaration - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); - - container.autowire(componentDeclarationCocktail); - - assert(componentDeclarationCocktail.componentA !is null, "Autowiring class with non-assignable declarations failed"); - } - - // Test autowire class with qualifier - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - auto componentX = container.resolve!(InterfaceA, ComponentX); - - auto monkeyShine = new MonkeyShine(); - container.autowire(monkeyShine); - - assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); - } - - // Test autowire class with multiple qualifiers - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - auto componentC = container.resolve!(InterfaceA, ComponentC); - auto componentX = container.resolve!(InterfaceA, ComponentX); - - auto bootstrapBootstrap = new BootstrapBootstrap(); - container.autowire(bootstrapBootstrap); - - assert(bootstrapBootstrap.componentX is componentX, "Autowiring class with multiple qualifiers failed"); - assert(bootstrapBootstrap.componentC is componentC, "Autowiring class with multiple qualifiers failed"); - } - - // Test getting instance from autowired registration will autowire instance - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - - auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), container).singleInstance(); - auto instance = cast(ComponentB) registration.getInstance(new AutowireInstantiationContext()); - - assert(instance.componentA !is null); - } - - // Test autowiring a dynamic array with all qualified types - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - - auto lord = new LordOfTheComponents(); - container.autowire(lord); - - assert(lord.components.length == 2, "Dynamic array was not autowired"); - } - - // Test autowiring new instance of singleinstance registration with newInstance UDA - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - - auto regularComponentA = container.resolve!ComponentA; - auto charlie = new ComponentCharlie(); - - container.autowire(charlie); - - assert(charlie.componentA !is regularComponentA, "Autowiring class with AssignNewInstance did not yield a different instance"); - } - - // Test autowiring members from base class - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - container.register!ComponentB; - container.register!ComponentZ; - - auto instance = new ComponentZ(); - container.autowire(instance); - - assert(instance.componentA !is null); - } -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 Mike Bierlee + * This software is licensed under the terms of the MIT license. + * The full terms of the license can be found in the LICENSE file. + */ + +import poodinis; + +import std.exception; + +version(unittest) { + class ComponentA {} + + class ComponentB { + public @Autowire ComponentA componentA; + } + + interface InterfaceA {} + + class ComponentC : InterfaceA {} + + class ComponentD { + public @Autowire InterfaceA componentC = null; + private @Autowire InterfaceA privateComponentC = null; + } + + class DummyAttribute{}; + + class ComponentE { + @DummyAttribute + public ComponentC componentC; + } + + class ComponentDeclarationCocktail { + alias noomer = int; + + @Autowire + public ComponentA componentA; + + public void doesNothing() { + } + + ~this(){ + } + } + + class ComponentX : InterfaceA {} + + class ComponentZ : ComponentB { + } + + class MonkeyShine { + @Autowire!ComponentX + public InterfaceA component; + } + + class BootstrapBootstrap { + @Autowire!ComponentX + public InterfaceA componentX; + + @Autowire!ComponentC + public InterfaceA componentC; + } + + class LordOfTheComponents { + @Autowire + public InterfaceA[] components; + } + class ComponentCharlie { + @Autowire + @AssignNewInstance + public ComponentA componentA; + } + + class OuttaTime { + @Autowire + @OptionalDependency + public InterfaceA interfaceA; + + @Autowire + @OptionalDependency + public ComponentA componentA; + + @Autowire + @OptionalDependency + public ComponentC[] componentCs; + } + + // Test autowiring concrete type to existing instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + auto componentB = new ComponentB(); + container.autowire(componentB); + assert(componentB !is null, "Autowirable dependency failed to autowire"); + } + + // Test autowiring interface type to existing instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + auto componentD = new ComponentD(); + container.autowire(componentD); + assert(componentD.componentC !is null, "Autowirable dependency failed to autowire"); + } + + // Test autowiring private members + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + auto componentD = new ComponentD(); + container.autowire(componentD); + assert(componentD.privateComponentC is componentD.componentC, "Autowire private dependency failed"); + } + + // Test autowiring will only happen once + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC).newInstance(); + auto componentD = new ComponentD(); + container.autowire(componentD); + auto expectedComponent = componentD.componentC; + container.autowire(componentD); + auto actualComponent = componentD.componentC; + assert(expectedComponent is actualComponent, "Autowiring the second time wired a different instance"); + } + + // Test autowiring unregistered type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto componentD = new ComponentD(); + assertThrown!(ResolveException)(container.autowire(componentD), "Autowiring unregistered type should throw ResolveException"); + } + + // Test autowiring member with non-autowire attribute does not autowire + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto componentE = new ComponentE(); + container.autowire(componentE); + assert(componentE.componentC is null, "Autowiring should not occur for members with attributes other than @Autowire"); + } + + // Test autowire class with alias declaration + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); + + container.autowire(componentDeclarationCocktail); + + assert(componentDeclarationCocktail.componentA !is null, "Autowiring class with non-assignable declarations failed"); + } + + // Test autowire class with qualifier + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + auto componentX = container.resolve!(InterfaceA, ComponentX); + + auto monkeyShine = new MonkeyShine(); + container.autowire(monkeyShine); + + assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); + } + + // Test autowire class with multiple qualifiers + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + auto componentC = container.resolve!(InterfaceA, ComponentC); + auto componentX = container.resolve!(InterfaceA, ComponentX); + + auto bootstrapBootstrap = new BootstrapBootstrap(); + container.autowire(bootstrapBootstrap); + + assert(bootstrapBootstrap.componentX is componentX, "Autowiring class with multiple qualifiers failed"); + assert(bootstrapBootstrap.componentC is componentC, "Autowiring class with multiple qualifiers failed"); + } + + // Test getting instance from autowired registration will autowire instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + + auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), container).singleInstance(); + auto instance = cast(ComponentB) registration.getInstance(new AutowireInstantiationContext()); + + assert(instance.componentA !is null); + } + + // Test autowiring a dynamic array with all qualified types + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + + auto lord = new LordOfTheComponents(); + container.autowire(lord); + + assert(lord.components.length == 2, "Dynamic array was not autowired"); + } + + // Test autowiring new instance of singleinstance registration with newInstance UDA + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + + auto regularComponentA = container.resolve!ComponentA; + auto charlie = new ComponentCharlie(); + + container.autowire(charlie); + + assert(charlie.componentA !is regularComponentA, "Autowiring class with AssignNewInstance did not yield a different instance"); + } + + // Test autowiring members from base class + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + container.register!ComponentB; + container.register!ComponentZ; + + auto instance = new ComponentZ(); + container.autowire(instance); + + assert(instance.componentA !is null); + } + + // Test autowiring optional depenencies + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto instance = new OuttaTime(); + + container.autowire(instance); + + assert(instance.interfaceA is null); + assert(instance.componentA is null); + assert(instance.componentCs is null); + } +}