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 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**

View file

@ -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 {

View file

@ -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)

View file

@ -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;

View file

@ -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;

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
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);
}
}

View file

@ -37,11 +37,6 @@ version(unittest) {
int nums;
}
// 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");
@ -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!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);
}