feat(ElementInjector): implement ElementInjector
This commit is contained in:
@ -1,5 +1,13 @@
|
||||
import {CONST} from "facade/lang";
|
||||
|
||||
/**
|
||||
* A parameter annotation that creates a synchronous eager dependency.
|
||||
*
|
||||
* class AComponent {
|
||||
* constructor(@Inject('aServiceToken') aService) {}
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export class Inject {
|
||||
@CONST()
|
||||
constructor(token) {
|
||||
@ -7,6 +15,16 @@ export class Inject {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter annotation that creates an asynchronous eager dependency.
|
||||
*
|
||||
* class AComponent {
|
||||
* constructor(@InjectPromise('aServiceToken') aServicePromise) {
|
||||
* aServicePromise.then(aService => ...);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export class InjectPromise {
|
||||
@CONST()
|
||||
constructor(token) {
|
||||
@ -14,9 +32,48 @@ export class InjectPromise {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter annotation that creates a synchronous lazy dependency.
|
||||
*
|
||||
* class AComponent {
|
||||
* constructor(@InjectLazy('aServiceToken') aServiceFn) {
|
||||
* aService = aServiceFn();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export class InjectLazy {
|
||||
@CONST()
|
||||
constructor(token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `DependencyAnnotation` is used by the framework to extend DI.
|
||||
*
|
||||
* Only annotations implementing `DependencyAnnotation` will be added
|
||||
* to the list of dependency properties.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* class Parent extends DependencyAnnotation {}
|
||||
* class NotDependencyProperty {}
|
||||
*
|
||||
* class AComponent {
|
||||
* constructor(@Parent @NotDependencyProperty aService:AService) {}
|
||||
* }
|
||||
*
|
||||
* will create the following dependency:
|
||||
*
|
||||
* new Dependency(Key.get(AService), [new Parent()])
|
||||
*
|
||||
* The framework can use `new Parent()` to handle the `aService` dependency
|
||||
* in a specific way.
|
||||
*
|
||||
*/
|
||||
export class DependencyAnnotation {
|
||||
@CONST()
|
||||
constructor() {
|
||||
}
|
||||
}
|
@ -7,10 +7,11 @@ export class Dependency {
|
||||
@FIELD('final key:Key')
|
||||
@FIELD('final asPromise:bool')
|
||||
@FIELD('final lazy:bool')
|
||||
constructor(key:Key, asPromise:boolean, lazy:boolean) {
|
||||
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List) {
|
||||
this.key = key;
|
||||
this.asPromise = asPromise;
|
||||
this.lazy = lazy;
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +45,7 @@ export class BindingBuilder {
|
||||
toValue(value):Binding {
|
||||
return new Binding(
|
||||
Key.get(this.token),
|
||||
(_) => value,
|
||||
() => value,
|
||||
[],
|
||||
false
|
||||
);
|
||||
@ -53,7 +54,7 @@ export class BindingBuilder {
|
||||
toFactory(factoryFunction:Function, dependencies:List = null):Binding {
|
||||
return new Binding(
|
||||
Key.get(this.token),
|
||||
reflector.convertToFactory(factoryFunction),
|
||||
factoryFunction,
|
||||
this._constructDependencies(factoryFunction, dependencies),
|
||||
false
|
||||
);
|
||||
@ -62,7 +63,7 @@ export class BindingBuilder {
|
||||
toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding {
|
||||
return new Binding(
|
||||
Key.get(this.token),
|
||||
reflector.convertToFactory(factoryFunction),
|
||||
factoryFunction,
|
||||
this._constructDependencies(factoryFunction, dependencies),
|
||||
true
|
||||
);
|
||||
@ -71,6 +72,6 @@ export class BindingBuilder {
|
||||
_constructDependencies(factoryFunction:Function, dependencies:List) {
|
||||
return isBlank(dependencies) ?
|
||||
reflector.dependencies(factoryFunction) :
|
||||
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false));
|
||||
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false, []));
|
||||
}
|
||||
}
|
||||
|
@ -3,3 +3,4 @@ export * from './injector';
|
||||
export * from './binding';
|
||||
export * from './key';
|
||||
export * from './module';
|
||||
export * from './exceptions';
|
||||
|
@ -5,6 +5,7 @@ import {ProviderError, NoProviderError, InvalidBindingError,
|
||||
import {Type, isPresent, isBlank} from 'facade/lang';
|
||||
import {Promise, PromiseWrapper} from 'facade/async';
|
||||
import {Key} from './key';
|
||||
import {reflector} from './reflector';
|
||||
|
||||
var _constructing = new Object();
|
||||
|
||||
@ -150,7 +151,7 @@ class _SyncInjectorStrategy {
|
||||
|
||||
_createInstance(key:Key, binding:Binding, deps:List) {
|
||||
try {
|
||||
var instance = binding.factory(deps);
|
||||
var instance = reflector.invoke(binding.factory, deps);
|
||||
this.injector._setInstance(key, instance);
|
||||
return instance;
|
||||
} catch (e) {
|
||||
@ -211,7 +212,7 @@ class _AsyncInjectorStrategy {
|
||||
try {
|
||||
var instance = this.injector._getInstance(key);
|
||||
if (!_isWaiting(instance)) return instance;
|
||||
return binding.factory(deps);
|
||||
return reflector.invoke(binding.factory, deps);
|
||||
} catch (e) {
|
||||
this.injector._clear(key);
|
||||
throw new InstantiationError(e, key);
|
||||
|
@ -1,26 +1,49 @@
|
||||
library facade.di.reflector;
|
||||
|
||||
import 'dart:mirrors';
|
||||
import 'annotations.dart' show Inject, InjectPromise, InjectLazy;
|
||||
import 'annotations.dart' show Inject, InjectPromise, InjectLazy, DependencyAnnotation;
|
||||
import 'key.dart' show Key;
|
||||
import 'binding.dart' show Dependency;
|
||||
import 'exceptions.dart' show NoAnnotationError;
|
||||
|
||||
class Reflector {
|
||||
Function factoryFor(Type type) {
|
||||
return _generateFactory(type);
|
||||
}
|
||||
|
||||
Function convertToFactory(Function factory) {
|
||||
return (args) => Function.apply(factory, args);
|
||||
}
|
||||
|
||||
Function _generateFactory(Type type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
|
||||
Function create = classMirror.newInstance;
|
||||
Symbol name = ctor.constructorName;
|
||||
return (args) => create(name, args).reflectee;
|
||||
int length = ctor.parameters.length;
|
||||
|
||||
switch (length) {
|
||||
case 0: return () =>
|
||||
create(name, []).reflectee;
|
||||
case 1: return (a1) =>
|
||||
create(name, [a1]).reflectee;
|
||||
case 2: return (a1, a2) =>
|
||||
create(name, [a1, a2]).reflectee;
|
||||
case 3: return (a1, a2, a3) =>
|
||||
create(name, [a1, a2, a3]).reflectee;
|
||||
case 4: return (a1, a2, a3, a4) =>
|
||||
create(name, [a1, a2, a3, a4]).reflectee;
|
||||
case 5: return (a1, a2, a3, a4, a5) =>
|
||||
create(name, [a1, a2, a3, a4, a5]).reflectee;
|
||||
case 6: return (a1, a2, a3, a4, a5, a6) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6]).reflectee;
|
||||
case 7: return (a1, a2, a3, a4, a5, a6, a7) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7]).reflectee;
|
||||
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8]).reflectee;
|
||||
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9]).reflectee;
|
||||
case 10: return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]).reflectee;
|
||||
};
|
||||
|
||||
throw "Factory cannot take more than 10 arguments";
|
||||
}
|
||||
|
||||
invoke(Function factory, List args) {
|
||||
return Function.apply(factory, args);
|
||||
}
|
||||
|
||||
List<Dependency> dependencies(typeOrFunc) {
|
||||
@ -38,16 +61,17 @@ class Reflector {
|
||||
var injectLazy = metadata.firstWhere((m) => m is InjectLazy, orElse: () => null);
|
||||
|
||||
if (inject != null) {
|
||||
return new Dependency(Key.get(inject.token), false, false);
|
||||
return new Dependency(Key.get(inject.token), false, false, []);
|
||||
|
||||
} else if (injectPromise != null) {
|
||||
return new Dependency(Key.get(injectPromise.token), true, false);
|
||||
return new Dependency(Key.get(injectPromise.token), true, false, []);
|
||||
|
||||
} else if (injectLazy != null) {
|
||||
return new Dependency(Key.get(injectLazy.token), false, true);
|
||||
return new Dependency(Key.get(injectLazy.token), false, true, []);
|
||||
|
||||
} else if (p.type.qualifiedName != #dynamic) {
|
||||
return new Dependency(Key.get(p.type.reflectedType), false, false);
|
||||
var depProps = metadata.where((m) => m is DependencyAnnotation).toList();
|
||||
return new Dependency(Key.get(p.type.reflectedType), false, false, depProps);
|
||||
|
||||
} else {
|
||||
throw new NoAnnotationError(typeOrFunc);
|
||||
|
@ -1,17 +1,43 @@
|
||||
import {Type, isPresent} from 'facade/lang';
|
||||
import {List} from 'facade/collection';
|
||||
import {Inject, InjectPromise, InjectLazy} from './annotations';
|
||||
import {Inject, InjectPromise, InjectLazy, DependencyAnnotation} from './annotations';
|
||||
import {Key} from './key';
|
||||
import {Dependency} from './binding';
|
||||
import {NoAnnotationError} from './exceptions';
|
||||
|
||||
class Reflector {
|
||||
factoryFor(type:Type):Function {
|
||||
return (args) => new type(...args);
|
||||
var length = type.parameters ? type.parameters.length : 0;
|
||||
switch (length) {
|
||||
case 0: return () =>
|
||||
new type();
|
||||
case 1: return (a1) =>
|
||||
new type(a1);
|
||||
case 2: return (a1, a2) =>
|
||||
new type(a1, a2);
|
||||
case 3: return (a1, a2, a3) =>
|
||||
new type(a1, a2, a3);
|
||||
case 4: return (a1, a2, a3, a4) =>
|
||||
new type(a1, a2, a3, a4);
|
||||
case 5: return (a1, a2, a3, a4, a5) =>
|
||||
new type(a1, a2, a3, a4, a5);
|
||||
case 6: return (a1, a2, a3, a4, a5, a6) =>
|
||||
new type(a1, a2, a3, a4, a5, a6);
|
||||
case 7: return (a1, a2, a3, a4, a5, a6, a7) =>
|
||||
new type(a1, a2, a3, a4, a5, a6, a7);
|
||||
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) =>
|
||||
new type(a1, a2, a3, a4, a5, a6, a7, a8);
|
||||
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
|
||||
new type(a1, a2, a3, a4, a5, a6, a7, a8, a9);
|
||||
case 10: return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
|
||||
new type(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
|
||||
};
|
||||
|
||||
throw "Factory cannot take more than 10 arguments";
|
||||
}
|
||||
|
||||
convertToFactory(factoryFunction:Function):Function {
|
||||
return (args) => factoryFunction(...args);
|
||||
invoke(factory:Function, args:List) {
|
||||
return factory(...args);
|
||||
}
|
||||
|
||||
dependencies(typeOrFunc):List {
|
||||
@ -23,31 +49,35 @@ class Reflector {
|
||||
|
||||
_extractToken(typeOrFunc, annotations) {
|
||||
var type;
|
||||
var depProps = [];
|
||||
|
||||
for (var paramAnnotation of annotations) {
|
||||
if (paramAnnotation instanceof Type) {
|
||||
type = paramAnnotation;
|
||||
|
||||
} else if (paramAnnotation instanceof Inject) {
|
||||
return this._createDependency(paramAnnotation.token, false, false);
|
||||
return this._createDependency(paramAnnotation.token, false, false, []);
|
||||
|
||||
} else if (paramAnnotation instanceof InjectPromise) {
|
||||
return this._createDependency(paramAnnotation.token, true, false);
|
||||
return this._createDependency(paramAnnotation.token, true, false, []);
|
||||
|
||||
} else if (paramAnnotation instanceof InjectLazy) {
|
||||
return this._createDependency(paramAnnotation.token, false, true);
|
||||
return this._createDependency(paramAnnotation.token, false, true, []);
|
||||
|
||||
} else if (paramAnnotation instanceof DependencyAnnotation) {
|
||||
depProps.push(paramAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(type)) {
|
||||
return this._createDependency(type, false, false);
|
||||
return this._createDependency(type, false, false, depProps);
|
||||
} else {
|
||||
throw new NoAnnotationError(typeOrFunc);
|
||||
}
|
||||
}
|
||||
|
||||
_createDependency(token, asPromise, lazy):Dependency {
|
||||
return new Dependency(Key.get(token), asPromise, lazy);
|
||||
_createDependency(token, asPromise, lazy, depProps):Dependency {
|
||||
return new Dependency(Key.get(token), asPromise, lazy, depProps);
|
||||
}
|
||||
}
|
||||
|
||||
|
23
modules/di/test/di/reflector_spec.js
Normal file
23
modules/di/test/di/reflector_spec.js
Normal file
@ -0,0 +1,23 @@
|
||||
import {ddescribe, describe, it, iit, expect} from 'test_lib/test_lib';
|
||||
import {Key, Inject, DependencyAnnotation} from 'di/di';
|
||||
import {CONST} from 'facade/lang';
|
||||
import {reflector, Token} from 'di/reflector';
|
||||
|
||||
class Parent extends DependencyAnnotation {
|
||||
@CONST()
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe("reflector", function () {
|
||||
describe("dependencies", function () {
|
||||
it('should collect annotations implementing DependencyAnnotation as properties', function () {
|
||||
function f(@Parent() arg:Function) {}
|
||||
|
||||
var dep = reflector.dependencies(f)[0];
|
||||
expect(dep.properties[0]).toBeAnInstanceOf(Parent);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user