From a0176273c580c93260e4cf0e96bfb28d1df2f5ee Mon Sep 17 00:00:00 2001 From: vsavkin Date: Mon, 6 Oct 2014 13:45:24 -0400 Subject: [PATCH] feat(injector): implement InjectLazy --- modules/di/src/annotations.js | 7 +++ modules/di/src/binding.js | 2 +- modules/di/src/injector.js | 16 ++++-- modules/di/src/key.js | 3 +- modules/di/src/reflector.dart | 19 ++++--- modules/di/src/reflector.es6 | 13 +++-- modules/di/test/di/injector_spec.js | 86 ++++++++++++++++++++--------- 7 files changed, 98 insertions(+), 48 deletions(-) diff --git a/modules/di/src/annotations.js b/modules/di/src/annotations.js index 63e903c91c..a9eb0514d3 100644 --- a/modules/di/src/annotations.js +++ b/modules/di/src/annotations.js @@ -12,4 +12,11 @@ export class InjectFuture { constructor(token){ this.token = token; } +} + +export class InjectLazy { + @CONST() + constructor(token){ + this.token = token; + } } \ No newline at end of file diff --git a/modules/di/src/binding.js b/modules/di/src/binding.js index 02c8ad8c4d..56b7ba7988 100644 --- a/modules/di/src/binding.js +++ b/modules/di/src/binding.js @@ -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)); } } \ No newline at end of file diff --git a/modules/di/src/injector.js b/modules/di/src/injector.js index 9c6d6abef2..6536516269 100644 --- a/modules/di/src/injector.js +++ b/modules/di/src/injector.js @@ -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); } diff --git a/modules/di/src/key.js b/modules/di/src/key.js index f2c5714035..f9b626f5cb 100644 --- a/modules/di/src/key.js +++ b/modules/di/src/key.js @@ -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; } } diff --git a/modules/di/src/reflector.dart b/modules/di/src/reflector.dart index 46677c3956..9b88d9c9a9 100644 --- a/modules/di/src/reflector.dart +++ b/modules/di/src/reflector.dart @@ -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); } diff --git a/modules/di/src/reflector.es6 b/modules/di/src/reflector.es6 index 2675346cf7..5c30839446 100644 --- a/modules/di/src/reflector.es6 +++ b/modules/di/src/reflector.es6 @@ -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); } } \ No newline at end of file diff --git a/modules/di/test/di/injector_spec.js b/modules/di/test/di/injector_spec.js index ff6e045015..3eb9b8b3f8 100644 --- a/modules/di/test/di/injector_spec.js +++ b/modules/di/test/di/injector_spec.js @@ -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); + }); + }); }); } \ No newline at end of file