Change getting of singleton dependency container to be thread-safe

This commit is contained in:
Mike Bierlee 2015-03-28 15:04:34 +01:00
parent f2eabe9797
commit 8d268846ed

View file

@ -1,282 +1,278 @@
/** /**
* Contains the implementation of the dependency container. * Contains the implementation of the dependency container.
* *
* Part of the Poodinis Dependency Injection framework. * Part of the Poodinis Dependency Injection framework.
* *
* Authors: * Authors:
* Mike Bierlee, m.bierlee@lostmoment.com * Mike Bierlee, m.bierlee@lostmoment.com
* Copyright: 2014-2015 Mike Bierlee * Copyright: 2014-2015 Mike Bierlee
* License: * License:
* This software is licensed under the terms of the MIT license. * This software is licensed under the terms of the MIT license.
* The full terms of the license can be found in the LICENSE file. * The full terms of the license can be found in the LICENSE file.
*/ */
module poodinis.container; module poodinis.container;
import std.string; import std.string;
import std.array; import std.array;
import std.algorithm; import std.algorithm;
import std.concurrency;
debug {
import std.stdio; debug {
} import std.stdio;
}
public import poodinis.registration;
public import poodinis.autowire; public import poodinis.registration;
public import poodinis.autowire;
/**
* Exception thrown when errors occur while resolving a type in a dependency container. /**
*/ * Exception thrown when errors occur while resolving a type in a dependency container.
class ResolveException : Exception { */
this(string message, TypeInfo resolveType) { class ResolveException : Exception {
super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); this(string message, TypeInfo resolveType) {
} super(format("Exception while resolving type %s: %s", resolveType.toString(), message));
} }
}
/**
* The dependency container maintains all dependencies registered with it. /**
* * The dependency container maintains all dependencies registered with it.
* Dependencies registered by a container can be resolved as long as they are still registered with the container. *
* Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of * Dependencies registered by a container can be resolved as long as they are still registered with the container.
* dependencies are created. Resolved dependencies will be autowired before being returned. * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of
* * dependencies are created. Resolved dependencies will be autowired before being returned.
* In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies. *
* You can still create new instances of this class for exceptional situations. * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies.
*/ * You can still create new instances of this class for exceptional situations.
class DependencyContainer { */
class DependencyContainer {
private static DependencyContainer instance; private Registration[][TypeInfo] registrations;
private Registration[][TypeInfo] registrations; private Registration[] autowireStack;
private Registration[] autowireStack; /**
* Register a dependency by concrete class type.
/** *
* Register a dependency by concrete class type. * A dependency registered by concrete class type can only be resolved by concrete class type.
* * No qualifiers can be used when resolving dependencies which are registered by concrete type.
* A dependency registered by concrete class type can only be resolved by concrete class type. *
* No qualifiers can be used when resolving dependencies which are registered by concrete type. * The default registration scope is "single instance" scope.
* *
* The default registration scope is "single instance" scope. * Returns:
* * A registration is returned which can be used to change the registration scope.
* Returns: *
* A registration is returned which can be used to change the registration scope. * Examples:
* * Register and resolve a class by concrete type:
* Examples: * ---
* Register and resolve a class by concrete type: * class Cat : Animal { ... }
* --- *
* class Cat : Animal { ... } * container.register!Cat;
* *
* container.register!Cat; * container.resolve!Cat;
* * container.resolve!(Animal, Cat); // Error! dependency is not registered by super type.
* container.resolve!Cat; * ---
* container.resolve!(Animal, Cat); // Error! dependency is not registered by super type. *
* --- * See_Also: singleInstance, newInstance, existingInstance
* */
* See_Also: singleInstance, newInstance, existingInstance public Registration register(ConcreteType)() {
*/ return register!(ConcreteType, ConcreteType)();
public Registration register(ConcreteType)() { }
return register!(ConcreteType, ConcreteType)();
} /**
* Register a dependency by super type.
/** *
* Register a dependency by super type. * A dependency registered by super type can only be resolved by super type. A qualifier is typically
* * used to resolve dependencies registered by super type.
* A dependency registered by super type can only be resolved by super type. A qualifier is typically *
* used to resolve dependencies registered by super type. * The default registration scope is "single instance" scope.
* *
* The default registration scope is "single instance" scope. * Examples:
* * Register and resolve by super type
* Examples: * ---
* Register and resolve by super type * class Cat : Animal { ... }
* --- *
* class Cat : Animal { ... } * container.register!(Animal, Cat);
* *
* container.register!(Animal, Cat); * container.resolve!(Animal, Cat);
* * container.resolve!Cat; // Error! dependency is not registered by concrete type.
* container.resolve!(Animal, Cat); * ---
* container.resolve!Cat; // Error! dependency is not registered by concrete type. *
* --- * See_Also: singleInstance, newInstance, existingInstance
* */
* See_Also: singleInstance, newInstance, existingInstance public Registration register(SuperType, ConcreteType : SuperType)() {
*/ TypeInfo registeredType = typeid(SuperType);
public Registration register(SuperType, ConcreteType : SuperType)() { TypeInfo_Class concreteType = typeid(ConcreteType);
TypeInfo registeredType = typeid(SuperType);
TypeInfo_Class concreteType = typeid(ConcreteType); debug(poodinisVerbose) {
writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString()));
debug(poodinisVerbose) { }
writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString()));
} auto existingRegistration = getExistingRegistration(registeredType, concreteType);
if (existingRegistration) {
auto existingRegistration = getExistingRegistration(registeredType, concreteType); return existingRegistration;
if (existingRegistration) { }
return existingRegistration;
} AutowiredRegistration!ConcreteType newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this);
newRegistration.singleInstance();
AutowiredRegistration!ConcreteType newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this); registrations[registeredType] ~= newRegistration;
newRegistration.singleInstance(); return newRegistration;
registrations[registeredType] ~= newRegistration; }
return newRegistration;
} private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) {
auto existingCandidates = registrationType in registrations;
private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) { if (existingCandidates) {
auto existingCandidates = registrationType in registrations; return getRegistration(*existingCandidates, qualifierType);
if (existingCandidates) { }
return getRegistration(*existingCandidates, qualifierType);
} return null;
}
return null;
} private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) {
foreach(existingRegistration ; candidates) {
private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) { if (existingRegistration.instantiatableType == concreteType) {
foreach(existingRegistration ; candidates) { return existingRegistration;
if (existingRegistration.instantiatableType == concreteType) { }
return existingRegistration; }
}
} return null;
}
return null;
} /**
* Resolve dependencies.
/** *
* Resolve dependencies. * Dependencies can only resolved using this method if they are registered by concrete type or the only
* * concrete type registered by super type.
* Dependencies can only resolved using this method if they are registered by concrete type or the only *
* concrete type registered by super type. * Resolved dependencies are automatically autowired before being returned.
* *
* Resolved dependencies are automatically autowired before being returned. * Returns:
* * An instance is returned which is created according to the registration scope with which they are registered.
* Returns: *
* An instance is returned which is created according to the registration scope with which they are registered. * Throws:
* * ResolveException when type is not registered.
* Throws: *
* ResolveException when type is not registered. * Examples:
* * Resolve dependencies registered by super type and concrete type:
* Examples: * ---
* Resolve dependencies registered by super type and concrete type: * class Cat : Animal { ... }
* --- * class Dog : Animal { ... }
* class Cat : Animal { ... } *
* class Dog : Animal { ... } * container.register!(Animal, Cat);
* * container.register!Dog;
* container.register!(Animal, Cat); *
* container.register!Dog; * container.resolve!Animal;
* * container.resolve!Dog;
* container.resolve!Animal; * ---
* container.resolve!Dog; * You cannot resolve a dependency when it is registered by multiple super types:
* --- * ---
* You cannot resolve a dependency when it is registered by multiple super types: * class Cat : Animal { ... }
* --- * class Dog : Animal { ... }
* class Cat : Animal { ... } *
* class Dog : Animal { ... } * container.register!(Animal, Cat);
* * container.register!(Animal, Dog);
* container.register!(Animal, Cat); *
* container.register!(Animal, Dog); * container.resolve!Animal; // Error: multiple candidates for type "Animal"
* * container.resolve!Dog; // Error: No type is registered by concrete type "Dog", only by super type "Animal"
* container.resolve!Animal; // Error: multiple candidates for type "Animal" * ---
* container.resolve!Dog; // Error: No type is registered by concrete type "Dog", only by super type "Animal" * You need to use the resolve method which allows you to specify a qualifier.
* --- */
* You need to use the resolve method which allows you to specify a qualifier. public RegistrationType resolve(RegistrationType)() {
*/ return resolve!(RegistrationType, RegistrationType)();
public RegistrationType resolve(RegistrationType)() { }
return resolve!(RegistrationType, RegistrationType)();
} /**
* Resolve dependencies using a qualifier.
/** *
* Resolve dependencies using a qualifier. * Dependencies can only resolved using this method if they are registered by super type.
* *
* Dependencies can only resolved using this method if they are registered by super type. * Resolved dependencies are automatically autowired before being returned.
* *
* Resolved dependencies are automatically autowired before being returned. * Returns:
* * An instance is returned which is created according to the registration scope with which they are registered.
* Returns: *
* An instance is returned which is created according to the registration scope with which they are registered. * Throws:
* * ResolveException when type is not registered or there are multiple candidates available for type.
* Throws: *
* ResolveException when type is not registered or there are multiple candidates available for type. * Examples:
* * Resolve dependencies registered by super type:
* Examples: * ---
* Resolve dependencies registered by super type: * class Cat : Animal { ... }
* --- * class Dog : Animal { ... }
* class Cat : Animal { ... } *
* class Dog : Animal { ... } * container.register!(Animal, Cat);
* * container.register!(Animal, Dog);
* container.register!(Animal, Cat); *
* container.register!(Animal, Dog); * container.resolve!(Animal, Cat);
* * container.resolve!(Animal, Dog);
* container.resolve!(Animal, Cat); * ---
* container.resolve!(Animal, Dog); */
* --- public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)() {
*/ TypeInfo resolveType = typeid(RegistrationType);
public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)() { TypeInfo qualifierType = typeid(QualifierType);
TypeInfo resolveType = typeid(RegistrationType);
TypeInfo qualifierType = typeid(QualifierType); debug(poodinisVerbose) {
writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString());
debug(poodinisVerbose) { }
writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString());
} auto candidates = resolveType in registrations;
if (!candidates) {
auto candidates = resolveType in registrations; throw new ResolveException("Type not registered.", resolveType);
if (!candidates) { }
throw new ResolveException("Type not registered.", resolveType);
} Registration registration = getQualifiedRegistration(resolveType, qualifierType, *candidates);
QualifierType instance;
Registration registration = getQualifiedRegistration(resolveType, qualifierType, *candidates);
QualifierType instance; if (!autowireStack.canFind(registration)) {
autowireStack ~= registration;
if (!autowireStack.canFind(registration)) { instance = cast(QualifierType) registration.getInstance(new AutowireInstantiationContext());
autowireStack ~= registration; autowireStack.popBack();
instance = cast(QualifierType) registration.getInstance(new AutowireInstantiationContext()); } else {
autowireStack.popBack(); auto autowireContext = new AutowireInstantiationContext();
} else { autowireContext.autowireInstance = false;
auto autowireContext = new AutowireInstantiationContext(); instance = cast(QualifierType) registration.getInstance(autowireContext);
autowireContext.autowireInstance = false; }
instance = cast(QualifierType) registration.getInstance(autowireContext);
} return instance;
}
return instance;
} private Registration getQualifiedRegistration(TypeInfo resolveType, TypeInfo qualifierType, Registration[] candidates) {
if (resolveType == qualifierType) {
private Registration getQualifiedRegistration(TypeInfo resolveType, TypeInfo qualifierType, Registration[] candidates) { if (candidates.length > 1) {
if (resolveType == qualifierType) { string candidateList = candidates.toConcreteTypeListString();
if (candidates.length > 1) { throw new ResolveException("Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", resolveType);
string candidateList = candidates.toConcreteTypeListString(); }
throw new ResolveException("Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", resolveType);
} return candidates[0];
}
return candidates[0];
} return getRegistration(candidates, qualifierType);
}
return getRegistration(candidates, qualifierType);
} /**
* Clears all dependency registrations managed by this container.
/** */
* Clears all dependency registrations managed by this container. public void clearAllRegistrations() {
*/ registrations.destroy();
public void clearAllRegistrations() { }
registrations.destroy();
} /**
* Removes a registered dependency by type.
/** *
* Removes a registered dependency by type. * A dependency can be removed either by super type or concrete type, depending on how they are registered.
* *
* A dependency can be removed either by super type or concrete type, depending on how they are registered. * Examples:
* * ---
* Examples: * container.removeRegistration!Animal;
* --- * ---
* container.removeRegistration!Animal; */
* --- public void removeRegistration(RegistrationType)() {
*/ registrations.remove(typeid(RegistrationType));
public void removeRegistration(RegistrationType)() { }
registrations.remove(typeid(RegistrationType));
} /**
* Returns a global singleton instance of a dependency container.
/** */
* Returns a global singleton instance of a dependency container. public static shared(DependencyContainer) getInstance() {
*/ static shared DependencyContainer instance;
public static DependencyContainer getInstance() { return initOnce!instance(new DependencyContainer());
if (instance is null) { }
instance = new DependencyContainer(); }
}
return instance;
}
}