ts.di
Version:
Typescript写的依赖注入库。An dependency-injection library for TypeScript.
184 lines (160 loc) • 6.79 kB
text/typescript
import "reflect-metadata";
export interface Constructor<T extends Object> extends Function {
new(...args): T;
readonly constructor: Function;
readonly prototype: Object;
}
// deprecated!, use Constructor instead.
export type Newable<T> = Constructor<T>;
@Injectable()
export class Injector {
static get<T>(ctor: Constructor<T>): T {
return Injector.getWithFollower(ctor, undefined);
}
/* Set a class as @Singleton() and give it a custom \a value for future retrieving.
* If there exist an instance of target class, and \a overwrite haven't set,
* setting will be failed and function will return false. Otherwise, newIt singleton
* instance will be set and true will be returned.
*/
static setSingleton<T>(ctor: Constructor<T>, val: T, overwrite?: boolean): boolean {
Injector.registerSingleton(ctor);
if (!overwrite && ctor[Injector.singletonInstance]) {
return false;
}
ctor[Injector.singletonInstance] = val;
return true;
}
static isSingletonExist<T>(ctor: Constructor<T>): boolean {
return !!ctor[Injector.singletonInstance];
}
private static getWithFollower<T>(ctor: Constructor<T>, fllower?: any[]): T {
// A class without @Injectable() decorated can be instantiate too
// **But that is not recommended**
if (Injector.strictDecorationCheck && !ctor[Injector.isInjectable]) {
throw new Error("To get a instance of " + ctor.name + ", you should decorate it with @Injectable().")
}
// There is a different instantiation for Singleton class.
if (ctor[Injector.isSingleton]) {
return ctor[Injector.singletonInstance] = ctor[Injector.singletonInstance] || Injector.create(ctor);
}
return Injector.create(ctor, fllower);
}
private static create<T>(ctor: Constructor<T>, follwer?: Function[]): T {
// If there is a factory for it
if (ctor[Injector.factorSymbol])
return ctor[Injector.factorSymbol]();
let argtypes = Reflect.getMetadata("design:paramtypes", ctor) || [];
// If it depends on nothing, just newIt it.
if (argtypes.length === 0) {
return new ctor();
}
// If it depends on something else, check if there is a dependency loop
let newFollower;
if (Injector.checkDependencyLoop) {
if (follwer && follwer.indexOf(ctor) != -1) {
throw new Error("Found dependency loop while trying to instantiate " + ctor.name + ". Please check your code.");
}
// Prepare for next dependency loop check
newFollower = (<Function[]>[ctor]).concat(follwer);
}
// Instantiate the arguments it's constructor needs
let args = [];
for (let i = 0; i < argtypes.length; i++) {
args.push(Injector.getWithFollower(argtypes[i], newFollower));
}
// newIt it
return new ctor(...args);
}
static registerInjectable<T>(ctor: Constructor<T>): Constructor<T> {
ctor[Injector.isInjectable] = true;
return ctor;
}
static registerSingleton<T>(ctor: Constructor<T>): Constructor<T> {
ctor[Injector.isInjectable] = true;
ctor[Injector.isSingleton] = true;
return ctor;
}
static injectDecoratorFactor(undefOrTypeFunc?: {<T>(): Constructor<T>}) {
if (undefOrTypeFunc) {
return (target, propertyKey) => {
Object.defineProperty(target, propertyKey, {
get: () => {
let obj = Injector.get(undefOrTypeFunc());
Object.defineProperty(target, propertyKey, obj);
return obj;
}
})
};
}
return (target, propertyKey) => {
Object.defineProperty(target, propertyKey, {
get: () => {
let obj = Injector.get(Reflect.getMetadata("design:type", target, propertyKey));
Object.defineProperty(target, propertyKey, obj);
return obj;
}
})
}
}
static factorOfDecoratorFactor<T>(ctor: Constructor<T>): MethodDecorator {
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
ctor[Injector.factorSymbol] = target[propertyKey];
}
}
private static factorSymbol = Symbol('Injector.factorSymbol');
private static isInjectable = Symbol('Injector.isInjectable');
private static isSingleton = Symbol('Injector.isSingleton');
private static singletonInstance = Symbol('Injector.singletonInstance');
public static checkDependencyLoop = false; // for performance considering, disable it by default.
public static strictDecorationCheck = true;
}
/* \brief @Injectable() marks a class injectable
*
* Use Injector.get() to get a instance of that class
* Calling Injector.get() multiple times on a @Injectable() decorated class
* will produce different instances.
*
*/
export function Injectable(): ClassDecorator {
return Injector.registerInjectable;
}
/* @Singleton() marks a singleton class.
* Use Injector.get() to get the instance of singleton class
*/
export function Singleton(): ClassDecorator {
return Injector.registerSingleton;
}
/* \brief @Inject() marks a property as injected property
*
* Unlike @Injectable() unable to instantiate class having dependency loop,
* @Inject(()=>Type) can instantiate dependencies like A->B->A as long as there is a
* @Singleton() class among them.
*
*/
export function Inject(TypeFunc?: {<T>(_?: any): Constructor<T>}): PropertyDecorator {
return Injector.injectDecoratorFactor(TypeFunc);
}
/* \brief @FactorOf(Class) tell Injector to use it(this decorated function) to create instance of \a Class.
*
* Example :
* \code ts
* @Singleton()
* class A {
* // it's almost impossible to create A using our Injector.
* // Because param val should provided by ourselves but not Injector.
* // So a factor function to create it is necessary.
* constructor(private val:number){}
* }
* class FactorUtils {
* @FactorOf(A)
* someFunctionToCreateA(){
* return {val:1234}
* }
* }
*
* Injector.get(A).val === 1234;// this will be true
* \endcode
*/
export function FactorOf<T>(ctor: Constructor<T>): MethodDecorator {
return Injector.factorOfDecoratorFactor(ctor);
}