Add pre-destruction

This commit is contained in:
Mike Bierlee 2016-12-17 23:09:56 +01:00
parent fbef764b48
commit b6dea95c0d
7 changed files with 192 additions and 123 deletions

View file

@ -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 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 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 @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 * FIX nullpointer exception in instance factory when debugging with poodinisVerbose
**Version 7.0.1** **Version 7.0.1**

View file

@ -257,9 +257,23 @@ class AutowiredRegistration(RegistrationType : Object) : Registration {
originatingContainer.autowire(instance); originatingContainer.autowire(instance);
} }
this.preDestructor = getPreDestructor(instance);
return 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 { class AutowireInstantiationContext : InstantiationContext {

View file

@ -91,6 +91,15 @@ public enum ResolveOption {
*/ */
struct PostConstruct {} 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. * The dependency container maintains all dependencies registered with it.
* *
@ -109,6 +118,10 @@ synchronized class DependencyContainer {
private RegistrationOption persistentRegistrationOptions; private RegistrationOption persistentRegistrationOptions;
private ResolveOption persistentResolveOptions; private ResolveOption persistentResolveOptions;
~this() {
clearAllRegistrations();
}
/** /**
* Register a dependency by concrete class type. * Register a dependency by concrete class type.
* *
@ -390,6 +403,9 @@ synchronized class DependencyContainer {
* Clears all dependency registrations managed by this container. * Clears all dependency registrations managed by this container.
*/ */
public void clearAllRegistrations() { public void clearAllRegistrations() {
foreach(registrationsOfType; registrations) {
callPreDestructorsOfRegistrations(registrationsOfType);
}
registrations.destroy(); registrations.destroy();
} }
@ -404,9 +420,20 @@ synchronized class DependencyContainer {
* --- * ---
*/ */
public void removeRegistration(RegistrationType)() { public void removeRegistration(RegistrationType)() {
auto registrationsOfType = *(typeid(RegistrationType) in registrations);
callPreDestructorsOfRegistrations(registrationsOfType);
registrations.remove(typeid(RegistrationType)); 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. * Returns a global singleton instance of a dependency container.
* Deprecated: create new instance with new keyword or implement your own singleton factory (method) * Deprecated: create new instance with new keyword or implement your own singleton factory (method)

View file

@ -22,6 +22,7 @@ class Registration {
private Registration linkedRegistration; private Registration linkedRegistration;
private shared(DependencyContainer) _originatingContainer; private shared(DependencyContainer) _originatingContainer;
private InstanceFactory _instanceFactory; private InstanceFactory _instanceFactory;
private void delegate() _preDestructor;
public @property registeredType() { public @property registeredType() {
return _registeredType; return _registeredType;
@ -39,6 +40,14 @@ class Registration {
return _instanceFactory; 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(TypeInfo registeredType, TypeInfo_Class instanceType, InstanceFactory instanceFactory, shared(DependencyContainer) originatingContainer) {
this._registeredType = registeredType; this._registeredType = registeredType;
this._instanceType = instanceType; this._instanceType = instanceType;

View file

@ -95,6 +95,13 @@ version(unittest) {
public ComponentA unrelated; 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 // Test autowiring concrete type to existing instance
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
@ -252,12 +259,6 @@ version(unittest) {
// Test autowiring class using value injection // Test autowiring class using value injection
unittest { unittest {
auto container = new shared DependencyContainer(); 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!(ValueInjector!int, TestInjector);
container.register!ComponentA; container.register!ComponentA;

View file

@ -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 // Test register concrete type
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
@ -704,16 +719,28 @@ version(unittest) {
// Test postconstruction happens after autowiring and value injection // Test postconstruction happens after autowiring and value injection
unittest { unittest {
class IntInjector : ValueInjector!int {
int get(string key) {
return 8783;
}
}
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!(ValueInjector!int, IntInjector); container.register!(ValueInjector!int, IntInjector);
container.register!PostConstructionDependency; container.register!PostConstructionDependency;
container.register!PostConstructWithAutowiring; container.register!PostConstructWithAutowiring;
auto instance = container.resolve!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);
}
} }

View file

@ -37,11 +37,6 @@ version(unittest) {
int nums; int nums;
} }
// Test injection of values
unittest {
auto container = new shared DependencyContainer();
container.register!MyConfig;
class IntInjector : ValueInjector!int { class IntInjector : ValueInjector!int {
public override int get(string key) { public override int get(string key) {
assert(key == "conf.stuffs"); assert(key == "conf.stuffs");
@ -63,6 +58,78 @@ version(unittest) {
} }
} }
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;
container.register!(ValueInjector!int, IntInjector); container.register!(ValueInjector!int, IntInjector);
container.register!(ValueInjector!string, StringInjector); container.register!(ValueInjector!string, StringInjector);
container.register!(ValueInjector!Thing, ThingInjector); container.register!(ValueInjector!Thing, ThingInjector);
@ -86,14 +153,7 @@ version(unittest) {
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!ConfigWithDefaults; container.register!ConfigWithDefaults;
container.register!(ValueInjector!int, DefaultIntInjector);
class IntInjector : ValueInjector!int {
public override int get(string key) {
throw new ValueNotAvailableException(key);
}
}
container.register!(ValueInjector!int, IntInjector);
auto instance = container.resolve!ConfigWithDefaults; auto instance = container.resolve!ConfigWithDefaults;
assert(instance.noms == 9); assert(instance.noms == 9);
@ -103,14 +163,7 @@ version(unittest) {
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!ConfigWithMandatory; container.register!ConfigWithMandatory;
container.register!(ValueInjector!int, MandatoryAvailableIntInjector);
class IntInjector : ValueInjector!int {
public override int get(string key) {
return 7466;
}
}
container.register!(ValueInjector!int, IntInjector);
auto instance = container.resolve!ConfigWithMandatory; auto instance = container.resolve!ConfigWithMandatory;
assert(instance.nums == 7466); assert(instance.nums == 7466);
@ -120,14 +173,7 @@ version(unittest) {
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!ConfigWithMandatory; container.register!ConfigWithMandatory;
container.register!(ValueInjector!int, MandatoryUnavailableIntInjector);
class IntInjector : ValueInjector!int {
public override int get(string key) {
throw new ValueNotAvailableException(key);
}
}
container.register!(ValueInjector!int, IntInjector);
assertThrown!ResolveException(container.resolve!ConfigWithMandatory); assertThrown!ResolveException(container.resolve!ConfigWithMandatory);
assertThrown!ValueInjectionException(autowire(container, new ConfigWithMandatory())); assertThrown!ValueInjectionException(autowire(container, new ConfigWithMandatory()));
@ -138,19 +184,8 @@ version(unittest) {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
auto dependency = new Dependency(); auto dependency = new Dependency();
container.register!Dependency.existingInstance(dependency); container.register!Dependency.existingInstance(dependency);
container.register!(ValueInjector!int, DependencyInjectedIntInjector);
class IntInjector : ValueInjector!int { auto injector = cast(DependencyInjectedIntInjector) container.resolve!(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);
assert(injector.dependency is dependency); assert(injector.dependency is dependency);
} }
@ -158,25 +193,8 @@ version(unittest) {
// Test injecting circular dependencies within value injectors // Test injecting circular dependencies within value injectors
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!(ValueInjector!int, CircularIntInjector);
class IntInjector : ValueInjector!int { auto injector = cast(CircularIntInjector) container.resolve!(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);
assert(injector.dependency is injector); assert(injector.dependency is injector);
assert(injector.get("whatever") == 3); assert(injector.get("whatever") == 3);
@ -185,23 +203,8 @@ version(unittest) {
// Test value injection within value injectors // Test value injection within value injectors
unittest { unittest {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!(ValueInjector!int, ValueInjectedIntInjector);
class IntInjector : ValueInjector!int { auto injector = cast(ValueInjectedIntInjector) container.resolve!(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);
assert(injector.count == 5); assert(injector.count == 5);
} }
@ -211,22 +214,8 @@ version(unittest) {
auto container = new shared DependencyContainer(); auto container = new shared DependencyContainer();
container.register!ConfigWithDefaults; container.register!ConfigWithDefaults;
class IntInjector : ValueInjector!int { container.register!(ValueInjector!int, DependencyValueInjectedIntInjector);
auto injector = cast(DependencyValueInjectedIntInjector) container.resolve!(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);
assert(injector.config.noms == 8899); assert(injector.config.noms == 8899);
} }