diff --git a/CHANGES.md b/CHANGES.md index 1a3c07d..0255d7f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Poodinis Changelog **Version NEXT** * ADD value injection. Members with UDA @Value will be attempted to be injected with a value-type. See tutorial and examples for more info. * ADD Phobos 2.072.1 forwards-compatibility for D/Phobos 2.066.1. This means you can use Poodinis with D/Phobos 2.066.1 compatible compilers such as GDC. +* ADD @PostConstruct UDA for marking methods which should be called after a dependency is resolved and autowired. * FIX nullpointer exception in instance factory when debugging with poodinisVerbose **Version 7.0.1** diff --git a/source/poodinis/container.d b/source/poodinis/container.d index fb66ba5..1496936 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -13,19 +13,21 @@ module poodinis.container; -import std.string; -import std.algorithm; -import std.concurrency; - -debug { - import std.stdio; -} - import poodinis.registration; import poodinis.autowire; import poodinis.context; import poodinis.factory; import poodinis.valueinjection; +import poodinis.polyfill; + +import std.string; +import std.algorithm; +import std.concurrency; +import std.traits; + +debug { + import std.stdio; +} /** * Exception thrown when errors occur while resolving a type in a dependency container. @@ -80,6 +82,15 @@ public enum ResolveOption { noResolveException = 1 << 1 } +/** + * Methods marked with this UDA within dependencies are called after that dependency + * is constructed by the dependency container. + * + * Multiple methods can be marked and will all be called after construction. The order in which + * methods are called is undetermined. Methods should have the signature void(void). + */ +struct PostConstruct {} + /** * The dependency container maintains all dependencies registered with it. * @@ -293,7 +304,9 @@ synchronized class DependencyContainer { Registration registration = getQualifiedRegistration(resolveType, qualifierType, cast(Registration[]) *candidates); try { - return resolveAutowiredInstance!QualifierType(registration); + QualifierType newInstance = resolveAutowiredInstance!QualifierType(registration); + callPostConstructors(newInstance); + return newInstance; } catch (ValueInjectionException e) { throw new ResolveException(e, resolveType); } @@ -363,6 +376,16 @@ synchronized class DependencyContainer { return getRegistration(candidates, qualifierType); } + private void callPostConstructors(Type)(Type instance) { + foreach (memberName; __traits(allMembers, Type)) { + static if (__traits(getProtection, __traits(getMember, instance, memberName)) == "public" + && isCallable!(__traits(getMember, instance, memberName)) + && hasUDA!(__traits(getMember, instance, memberName), PostConstruct)) { + __traits(getMember, instance, memberName)(); + } + } + } + /** * Clears all dependency registrations managed by this container. */ diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index fadc300..0c554b7 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -162,6 +162,44 @@ version(unittest) { this(Ola ola) {} } + class PostConstructionDependency { + public bool postConstructWasCalled = false; + + @PostConstruct + public void callMeMaybe() { + postConstructWasCalled = true; + } + } + + class ChildOfPostConstruction : PostConstructionDependency {} + + interface ThereWillBePostConstruction { + @PostConstruct + void constructIt(); + } + + class ButThereWontBe : ThereWillBePostConstruction { + public bool postConstructWasCalled = false; + + public override void constructIt() { + postConstructWasCalled = true; + } + } + + class PostConstructWithAutowiring { + @Autowire + private PostConstructionDependency dependency; + + @Value("") + private int theNumber = 1; + + @PostConstruct + public void doIt() { + assert(theNumber == 8783); + assert(dependency !is null); + } + } + // Test register concrete type unittest { auto container = new shared DependencyContainer(); @@ -636,4 +674,46 @@ version(unittest) { container.register!Hello; container.resolve!Hello; } + + // Test PostConstruct method is called after resolving a dependency + unittest { + auto container = new shared DependencyContainer(); + container.register!PostConstructionDependency; + + auto instance = container.resolve!PostConstructionDependency; + assert(instance.postConstructWasCalled == true); + } + + // Test PostConstruct of base type is called + unittest { + auto container = new shared DependencyContainer(); + container.register!ChildOfPostConstruction; + + auto instance = container.resolve!ChildOfPostConstruction; + assert(instance.postConstructWasCalled == true); + } + + // Test PostConstruct of class implementing interface is not called + unittest { + auto container = new shared DependencyContainer(); + container.register!ButThereWontBe; + + auto instance = container.resolve!ButThereWontBe; + assert(instance.postConstructWasCalled == false); + } + + // Test postconstruction happens after autowiring and value injection + unittest { + class IntInjector : ValueInjector!int { + int get(string key) { + return 8783; + } + } + + auto container = new shared DependencyContainer(); + container.register!(ValueInjector!int, IntInjector); + container.register!PostConstructionDependency; + container.register!PostConstructWithAutowiring; + auto instance = container.resolve!PostConstructWithAutowiring; + } }