diff --git a/CHANGES.md b/CHANGES.md index 0255d7f..9913922 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ Poodinis Changelog * 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. +* ADD @PreDestroy UDA for marking methods which should be called when the container loses a dependency's registration. It is called when +removeRegistration or clearAllRegistrations is called. It is also called when the container is destroyed. * FIX nullpointer exception in instance factory when debugging with poodinisVerbose **Version 7.0.1** diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index 61715b4..7b3c728 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -257,9 +257,23 @@ class AutowiredRegistration(RegistrationType : Object) : Registration { originatingContainer.autowire(instance); } + this.preDestructor = getPreDestructor(instance); + return instance; } + private void delegate() getPreDestructor(RegistrationType instance) { + void delegate() preDestructor = null; + foreach (memberName; __traits(allMembers, RegistrationType)) { + static if (__traits(getProtection, __traits(getMember, instance, memberName)) == "public" + && isCallable!(__traits(getMember, instance, memberName)) + && hasUDA!(__traits(getMember, instance, memberName), PreDestroy)) { + preDestructor = &__traits(getMember, instance, memberName); + } + } + + return preDestructor; + } } class AutowireInstantiationContext : InstantiationContext { diff --git a/source/poodinis/container.d b/source/poodinis/container.d index 1496936..ab08e52 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -91,6 +91,15 @@ public enum ResolveOption { */ struct PostConstruct {} +/** + * Methods marked with this UDA within dependencies are called before the container + * loses the dependency's registration. + * + * This method is called when removeRegistration or clearAllRegistrations is called. + * It will also be called when the container's destructor is called. + */ +struct PreDestroy {} + /** * The dependency container maintains all dependencies registered with it. * @@ -109,6 +118,10 @@ synchronized class DependencyContainer { private RegistrationOption persistentRegistrationOptions; private ResolveOption persistentResolveOptions; + ~this() { + clearAllRegistrations(); + } + /** * Register a dependency by concrete class type. * @@ -390,6 +403,9 @@ synchronized class DependencyContainer { * Clears all dependency registrations managed by this container. */ public void clearAllRegistrations() { + foreach(registrationsOfType; registrations) { + callPreDestructorsOfRegistrations(registrationsOfType); + } registrations.destroy(); } @@ -404,9 +420,20 @@ synchronized class DependencyContainer { * --- */ public void removeRegistration(RegistrationType)() { + auto registrationsOfType = *(typeid(RegistrationType) in registrations); + callPreDestructorsOfRegistrations(registrationsOfType); registrations.remove(typeid(RegistrationType)); } + private void callPreDestructorsOfRegistrations(shared(Registration[]) registrations) { + foreach(registration; registrations) { + Registration unsharedRegistration = cast(Registration) registration; + if (unsharedRegistration.preDestructor !is null) { + unsharedRegistration.preDestructor()(); + } + } + } + /** * Returns a global singleton instance of a dependency container. * Deprecated: create new instance with new keyword or implement your own singleton factory (method) diff --git a/source/poodinis/registration.d b/source/poodinis/registration.d index 6c9b0b6..f7fed37 100644 --- a/source/poodinis/registration.d +++ b/source/poodinis/registration.d @@ -22,6 +22,7 @@ class Registration { private Registration linkedRegistration; private shared(DependencyContainer) _originatingContainer; private InstanceFactory _instanceFactory; + private void delegate() _preDestructor; public @property registeredType() { return _registeredType; @@ -39,6 +40,14 @@ class Registration { return _instanceFactory; } + public @property preDestructor() { + return _preDestructor; + } + + protected @property preDestructor(void delegate() preDestructor) { + _preDestructor = preDestructor; + } + this(TypeInfo registeredType, TypeInfo_Class instanceType, InstanceFactory instanceFactory, shared(DependencyContainer) originatingContainer) { this._registeredType = registeredType; this._instanceType = instanceType; diff --git a/test/poodinis/autowiretest.d b/test/poodinis/autowiretest.d index cbfe613..8743a92 100644 --- a/test/poodinis/autowiretest.d +++ b/test/poodinis/autowiretest.d @@ -95,6 +95,13 @@ version(unittest) { public ComponentA unrelated; } + class TestInjector : ValueInjector!int { + public override int get(string key) { + assert(key == "values.int"); + return 8; + } + } + // Test autowiring concrete type to existing instance unittest { auto container = new shared DependencyContainer(); @@ -252,12 +259,6 @@ version(unittest) { // Test autowiring class using value injection unittest { auto container = new shared DependencyContainer(); - class TestInjector : ValueInjector!int { - public override int get(string key) { - assert(key == "values.int"); - return 8; - } - } container.register!(ValueInjector!int, TestInjector); container.register!ComponentA; diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 0c554b7..6d3f705 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -200,6 +200,21 @@ version(unittest) { } } + class PreDestroyerOfFates { + public bool preDestroyWasCalled = false; + + @PreDestroy + public void callMeMaybe() { + preDestroyWasCalled = true; + } + } + + class IntInjector : ValueInjector!int { + int get(string key) { + return 8783; + } + } + // Test register concrete type unittest { auto container = new shared DependencyContainer(); @@ -704,16 +719,28 @@ version(unittest) { // 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; } + + // Test PreDestroy is called when removing a registration + unittest { + auto container = new shared DependencyContainer(); + container.register!PreDestroyerOfFates; + auto instance = container.resolve!PreDestroyerOfFates; + container.removeRegistration!PreDestroyerOfFates; + assert(instance.preDestroyWasCalled == true); + } + + // Test PreDestroy is called when removing all registrations + unittest { + auto container = new shared DependencyContainer(); + container.register!PreDestroyerOfFates; + auto instance = container.resolve!PreDestroyerOfFates; + container.clearAllRegistrations(); + assert(instance.preDestroyWasCalled == true); + } } diff --git a/test/poodinis/valueinjectiontest.d b/test/poodinis/valueinjectiontest.d index df6e8da..e848cfd 100644 --- a/test/poodinis/valueinjectiontest.d +++ b/test/poodinis/valueinjectiontest.d @@ -37,32 +37,99 @@ version(unittest) { int nums; } + class IntInjector : ValueInjector!int { + public override int get(string key) { + assert(key == "conf.stuffs"); + return 364; + } + } + + class StringInjector : ValueInjector!string { + public override string get(string key) { + assert(key == "conf.name"); + return "Le Chef"; + } + } + + class ThingInjector : ValueInjector!Thing { + public override Thing get(string key) { + assert(key == "conf.thing"); + return Thing(8899); + } + } + + class DefaultIntInjector : ValueInjector!int { + public override int get(string key) { + throw new ValueNotAvailableException(key); + } + } + + class MandatoryAvailableIntInjector : ValueInjector!int { + public override int get(string key) { + return 7466; + } + } + + class MandatoryUnavailableIntInjector : ValueInjector!int { + public override int get(string key) { + throw new ValueNotAvailableException(key); + } + } + + class DependencyInjectedIntInjector : ValueInjector!int { + @Autowire + public Dependency dependency; + + public override int get(string key) { + return 2345; + } + } + + class CircularIntInjector : ValueInjector!int { + @Autowire + public ValueInjector!int dependency; + + private int count = 0; + + public override int get(string key) { + count += 1; + if (count >= 3) { + return count; + } + return dependency.get(key); + } + } + + class ValueInjectedIntInjector : ValueInjector!int { + @Value("five") + public int count = 0; + + public override int get(string key) { + if (key == "five") { + return 5; + } + + return count; + } + } + + class DependencyValueInjectedIntInjector : ValueInjector!int { + @Autowire + public ConfigWithDefaults config; + + public override int get(string key) { + if (key == "conf.missing") { + return 8899; + } + + return 0; + } + } + // Test injection of values unittest { auto container = new shared DependencyContainer(); container.register!MyConfig; - - class IntInjector : ValueInjector!int { - public override int get(string key) { - assert(key == "conf.stuffs"); - return 364; - } - } - - class StringInjector : ValueInjector!string { - public override string get(string key) { - assert(key == "conf.name"); - return "Le Chef"; - } - } - - class ThingInjector : ValueInjector!Thing { - public override Thing get(string key) { - assert(key == "conf.thing"); - return Thing(8899); - } - } - container.register!(ValueInjector!int, IntInjector); container.register!(ValueInjector!string, StringInjector); container.register!(ValueInjector!Thing, ThingInjector); @@ -86,14 +153,7 @@ version(unittest) { unittest { auto container = new shared DependencyContainer(); container.register!ConfigWithDefaults; - - class IntInjector : ValueInjector!int { - public override int get(string key) { - throw new ValueNotAvailableException(key); - } - } - - container.register!(ValueInjector!int, IntInjector); + container.register!(ValueInjector!int, DefaultIntInjector); auto instance = container.resolve!ConfigWithDefaults; assert(instance.noms == 9); @@ -103,14 +163,7 @@ version(unittest) { unittest { auto container = new shared DependencyContainer(); container.register!ConfigWithMandatory; - - class IntInjector : ValueInjector!int { - public override int get(string key) { - return 7466; - } - } - - container.register!(ValueInjector!int, IntInjector); + container.register!(ValueInjector!int, MandatoryAvailableIntInjector); auto instance = container.resolve!ConfigWithMandatory; assert(instance.nums == 7466); @@ -120,14 +173,7 @@ version(unittest) { unittest { auto container = new shared DependencyContainer(); container.register!ConfigWithMandatory; - - class IntInjector : ValueInjector!int { - public override int get(string key) { - throw new ValueNotAvailableException(key); - } - } - - container.register!(ValueInjector!int, IntInjector); + container.register!(ValueInjector!int, MandatoryUnavailableIntInjector); assertThrown!ResolveException(container.resolve!ConfigWithMandatory); assertThrown!ValueInjectionException(autowire(container, new ConfigWithMandatory())); @@ -138,19 +184,8 @@ version(unittest) { auto container = new shared DependencyContainer(); auto dependency = new Dependency(); container.register!Dependency.existingInstance(dependency); - - class IntInjector : ValueInjector!int { - - @Autowire - public Dependency dependency; - - public override int get(string key) { - return 2345; - } - } - - container.register!(ValueInjector!int, IntInjector); - auto injector = cast(IntInjector) container.resolve!(ValueInjector!int); + container.register!(ValueInjector!int, DependencyInjectedIntInjector); + auto injector = cast(DependencyInjectedIntInjector) container.resolve!(ValueInjector!int); assert(injector.dependency is dependency); } @@ -158,25 +193,8 @@ version(unittest) { // Test injecting circular dependencies within value injectors unittest { auto container = new shared DependencyContainer(); - - class IntInjector : ValueInjector!int { - - @Autowire - public ValueInjector!int dependency; - - private int count = 0; - - public override int get(string key) { - count += 1; - if (count >= 3) { - return count; - } - return dependency.get(key); - } - } - - container.register!(ValueInjector!int, IntInjector); - auto injector = cast(IntInjector) container.resolve!(ValueInjector!int); + container.register!(ValueInjector!int, CircularIntInjector); + auto injector = cast(CircularIntInjector) container.resolve!(ValueInjector!int); assert(injector.dependency is injector); assert(injector.get("whatever") == 3); @@ -185,23 +203,8 @@ version(unittest) { // Test value injection within value injectors unittest { auto container = new shared DependencyContainer(); - - class IntInjector : ValueInjector!int { - - @Value("five") - public int count = 0; - - public override int get(string key) { - if (key == "five") { - return 5; - } - - return count; - } - } - - container.register!(ValueInjector!int, IntInjector); - auto injector = cast(IntInjector) container.resolve!(ValueInjector!int); + container.register!(ValueInjector!int, ValueInjectedIntInjector); + auto injector = cast(ValueInjectedIntInjector) container.resolve!(ValueInjector!int); assert(injector.count == 5); } @@ -211,22 +214,8 @@ version(unittest) { auto container = new shared DependencyContainer(); container.register!ConfigWithDefaults; - class IntInjector : ValueInjector!int { - - @Autowire - public ConfigWithDefaults config; - - public override int get(string key) { - if (key == "conf.missing") { - return 8899; - } - - return 0; - } - } - - container.register!(ValueInjector!int, IntInjector); - auto injector = cast(IntInjector) container.resolve!(ValueInjector!int); + container.register!(ValueInjector!int, DependencyValueInjectedIntInjector); + auto injector = cast(DependencyValueInjectedIntInjector) container.resolve!(ValueInjector!int); assert(injector.config.noms == 8899); }