feat(injector): implement InjectLazy

This commit is contained in:
vsavkin 2014-10-06 13:45:24 -04:00
parent e02cdfe733
commit a0176273c5
7 changed files with 98 additions and 48 deletions

View File

@ -12,4 +12,11 @@ export class InjectFuture {
constructor(token){
this.token = token;
}
}
export class InjectLazy {
@CONST()
constructor(token){
this.token = token;
}
}

View File

@ -59,6 +59,6 @@ export class BindingBuilder {
}
_constructDependencies(deps:List) {
return ListWrapper.map(deps, (t) => new Dependency(Key.get(t), false));
return ListWrapper.map(deps, (t) => new Dependency(Key.get(t), false, false));
}
}

View File

@ -37,11 +37,11 @@ export class Injector {
}
getByKey(key:Key) {
return this._getByKey(key, false);
return this._getByKey(key, false, false);
}
asyncGetByKey(key:Key) {
return this._getByKey(key, true);
return this._getByKey(key, true, false);
}
createChild(bindings:List):Injector {
@ -61,7 +61,11 @@ export class Injector {
return ListWrapper.createFixedSize(Key.numberOfKeys() + 1);
}
_getByKey(key:Key, returnFuture) {
_getByKey(key:Key, returnFuture, returnLazy) {
if (returnLazy) {
return () => this._getByKey(key, returnFuture, false);
}
var strategy = returnFuture ? this._asyncStrategy : this._syncStrategy;
var instance = strategy.readFromCache(key);
@ -71,7 +75,7 @@ export class Injector {
if (isPresent(instance)) return instance;
if (isPresent(this._parent)) {
return this._parent._getByKey(key, returnFuture);
return this._parent._getByKey(key, returnFuture, returnLazy);
}
throw new NoProviderError(key);
}
@ -129,7 +133,7 @@ class _SyncInjectorStrategy {
_resolveDependencies(key:Key, binding:Binding) {
try {
var getDependency = d => this.injector._getByKey(d.key, d.asFuture);
var getDependency = d => this.injector._getByKey(d.key, d.asFuture, d.lazy);
return ListWrapper.map(binding.dependencies, getDependency);
} catch (e) {
if (e instanceof ProviderError) e.addKey(key);
@ -184,7 +188,7 @@ class _AsyncInjectorStrategy {
}
_resolveDependencies(key:Key, binding:Binding):List {
var getDependency = d => this.injector._getByKey(d.key, true);
var getDependency = d => this.injector._getByKey(d.key, true, d.lazy);
return ListWrapper.map(binding.dependencies, getDependency);
}

View File

@ -5,9 +5,10 @@ var _id = 0;
//TODO: vsavkin: move to binding once cyclic deps are supported
export class Dependency {
constructor(key:Key, asFuture){
constructor(key:Key, asFuture, lazy){
this.key = key;
this.asFuture = asFuture;
this.lazy = lazy;
}
}

View File

@ -1,7 +1,7 @@
library facade.di.reflector;
import 'dart:mirrors';
import 'annotations.dart' show Inject, InjectFuture;
import 'annotations.dart' show Inject, InjectFuture, InjectLazy;
import 'key.dart' show Key, Dependency;
import 'exceptions.dart' show NoAnnotationError;
@ -31,15 +31,18 @@ class Reflector {
final metadata = p.metadata.map((m) => m.reflectee);
var inject = metadata.where((m) => m is Inject);
var injectFuture = metadata.where((m) => m is InjectFuture);
var inject = metadata.firstWhere((m) => m is Inject, orElse: () => null);
var injectFuture = metadata.firstWhere((m) => m is InjectFuture, orElse: () => null);
var injectLazy = metadata.firstWhere((m) => m is InjectLazy, orElse: () => null);
if (inject.isNotEmpty) {
return new Dependency(Key.get(inject.first.token), false);
} else if (injectFuture.isNotEmpty) {
return new Dependency(Key.get(injectFuture.first.token), true);
if (inject != null) {
return new Dependency(Key.get(inject.token), false, false);
} else if (injectFuture != null) {
return new Dependency(Key.get(injectFuture.token), true, false);
} else if (injectLazy != null) {
return new Dependency(Key.get(injectLazy.token), false, true);
} else if (p.type.qualifiedName != #dynamic) {
return new Dependency(Key.get(p.type.reflectedType), false);
return new Dependency(Key.get(p.type.reflectedType), false, false);
} else {
throw new NoAnnotationError(type);
}

View File

@ -1,5 +1,5 @@
import {Type, isPresent} from 'facade/lang';
import {Inject, InjectFuture} from './annotations';
import {Inject, InjectFuture, InjectLazy} from './annotations';
import {Dependency, Key} from './key';
import {NoAnnotationError} from './exceptions';
@ -27,10 +27,13 @@ export class Reflector {
type = paramAnnotation;
} else if (paramAnnotation instanceof Inject) {
return this._createDependency(paramAnnotation.token, false);
return this._createDependency(paramAnnotation.token, false, false);
} else if (paramAnnotation instanceof InjectFuture) {
return this._createDependency(paramAnnotation.token, true);
return this._createDependency(paramAnnotation.token, true, false);
} else if (paramAnnotation instanceof InjectLazy) {
return this._createDependency(paramAnnotation.token, false, true);
}
}
@ -41,7 +44,7 @@ export class Reflector {
}
}
_createDependency(token, asFuture):Dependency {
return new Dependency(Key.get(token), asFuture);
_createDependency(token, asFuture, lazy):Dependency {
return new Dependency(Key.get(token), asFuture, lazy);
}
}

View File

@ -1,5 +1,5 @@
import {describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
import {Injector, Inject, bind} from 'di/di';
import {describe, ddescribe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
import {Injector, Inject, InjectLazy, bind} from 'di/di';
class Engine {}
class BrokenEngine {
@ -19,6 +19,12 @@ class Car {
}
}
class CarWithLazyEngine {
constructor(@InjectLazy(Engine) engineFactory) {
this.engineFactory = engineFactory;
}
}
class CarWithDashboard {
constructor(engine:Engine, dashboard:Dashboard) {
this.engine = engine;
@ -118,31 +124,6 @@ export function main() {
expect(() => new Injector([bind("blah")])).toThrowError('Invalid binding blah');
});
describe("child", function () {
it('should load instances from parent injector', function() {
var parent = new Injector([Engine]);
var child = parent.createChild([]);
var engineFromParent = parent.get(Engine);
var engineFromChild = child.get(Engine);
expect(engineFromChild).toBe(engineFromParent);
});
it('should create new instance in a child injector', function() {
var parent = new Injector([Engine]);
var child = parent.createChild([
bind(Engine).toClass(TurboEngine)
]);
var engineFromParent = parent.get(Engine);
var engineFromChild = child.get(Engine);
expect(engineFromParent).not.toBe(engineFromChild);
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
});
it('should provide itself', function() {
var parent = new Injector([]);
var child = parent.createChild([]);
@ -184,5 +165,56 @@ export function main() {
expect(e.message).toContain("Error during instantiation of Engine! (Car -> Engine)");
}
});
describe("child", function () {
it('should load instances from parent injector', function() {
var parent = new Injector([Engine]);
var child = parent.createChild([]);
var engineFromParent = parent.get(Engine);
var engineFromChild = child.get(Engine);
expect(engineFromChild).toBe(engineFromParent);
});
it('should create new instance in a child injector', function() {
var parent = new Injector([Engine]);
var child = parent.createChild([
bind(Engine).toClass(TurboEngine)
]);
var engineFromParent = parent.get(Engine);
var engineFromChild = child.get(Engine);
expect(engineFromParent).not.toBe(engineFromChild);
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
});
describe("lazy", function () {
it("should create dependencies lazily", function () {
var injector = new Injector([
Engine,
CarWithLazyEngine
]);
var car = injector.get(CarWithLazyEngine);
expect(car.engineFactory()).toBeAnInstanceOf(Engine);
});
it("should cache instance created lazily", function () {
var injector = new Injector([
Engine,
CarWithLazyEngine
]);
var car = injector.get(CarWithLazyEngine);
var e1 = car.engineFactory();
var e2 = car.engineFactory();
expect(e1).toBe(e2);
});
});
});
}