开篇

随着javaEE的spring框架的兴起,依赖注入(IoC)的概念彻底深入人心,它彻底改变了我们的编码模式和思维。在IoC之前,我们在程序中需要创建一个对象很简单也很直接,就是在代码中new Object即可,有我们自己负责创建、维护、修改和删除,也就是说,我们控制了对象的整个生命周期,直到对象没有被引用,被回收。诚然,当创建或者维护的对象数量较少时,这种做法无可厚非,但是当一个大项目中需要创建大数量级的对象时,仅仅依靠程序员来进行维护所有对象,这是难以做到的,特别是如果想在程序的整个生命周期内复用一些对象,我们需要自己写一个缓存模块对所有对象进行缓存,这加大了复杂度。当然,IoC的好处并不仅限于此,它也降低了对依赖的耦合度,不必在代码中进行引用或者传参即可操作依赖。

在js中,我们可以这样引入依赖

  1. 使用全局变量引用
  2. 在需要的地方通过函数参数传递

使用全局变量的坏处自不必说,污染了全局的名字空间,而通过函参传递引用,也可以通过两种方法实现:

  1. 闭包传递
  2. 后台解析出依赖对象,并通过Function.prototype.call进行传参

而在AngularJS中,依赖注入是通过后者实现的,接下来的几节将会介绍IoC模块的具体实现。

获取依赖

  1. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  2. var FN_ARG_SPLIT = /,/;
  3. // 获取服务名
  4. var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
  5. var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  6. var $injectorMinErr = minErr('$injector');
  7.  
  8. function anonFn(fn) {
  9. // For anonymous functions, showing at the very least the function signature can help in
  10. // debugging.
  11. var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
  12. args = fnText.match(FN_ARGS);
  13. if (args) {
  14. return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
  15. }
  16. return 'fn';
  17. }
  18.  
  19. function annotate(fn, strictDi, name) {
  20. var $inject,
  21. fnText,
  22. argDecl,
  23. last;
  24.  
  25. if (typeof fn === 'function') {
  26. if (!($inject = fn.$inject)) {
  27. $inject = [];
  28. if (fn.length) {
  29. if (strictDi) {
  30. if (!isString(name) || !name) {
  31. name = fn.name || anonFn(fn);
  32. }
  33. throw $injectorMinErr('strictdi',
  34. '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
  35. }
  36. fnText = fn.toString().replace(STRIP_COMMENTS, '');
  37. argDecl = fnText.match(FN_ARGS);
  38. forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
  39. arg.replace(FN_ARG, function(all, underscore, name) {
  40. $inject.push(name);
  41. });
  42. });
  43. }
  44. fn.$inject = $inject;
  45. }
  46. } else if (isArray(fn)) {
  47. last = fn.length - 1;
  48. assertArgFn(fn[last], 'fn');
  49. $inject = fn.slice(0, last);
  50. } else {
  51. assertArgFn(fn, 'fn', true);
  52. }
  53. return $inject;
  54. }

annotate函数通过对入参进行针对性分析,若传递的是一个函数,则依赖模块作为入参传递,此时可通过序列化函数进行正则匹配,获取依赖模块的名称并存入$inject数组中返回,另外,通过函数入参传递依赖的方式在严格模式下执行会抛出异常;第二种依赖传递则是通过数组的方式,数组的最后一个元素是需要使用依赖的函数。annotate函数最终返回解析的依赖名称。

注入器的创建

AngularJS的API也提供了$injector部分,通过$injector可以使用get,has,instantiate,invoke以及上节提到的annotate等方法,通过源码可以更清晰的理解。

  1. function createInternalInjector(cache, factory) {
  2. // 对服务注入器 providerInjector而言,只根据服务名获取服务,factory会抛出异常
  3. function getService(serviceName, caller) {
  4. if (cache.hasOwnProperty(serviceName)) {
  5. if (cache[serviceName] === INSTANTIATING) {
  6. throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
  7. serviceName + ' <- ' + path.join(' <- '));
  8. }
  9. return cache[serviceName];
  10. } else {
  11. try {
  12. path.unshift(serviceName);
  13. cache[serviceName] = INSTANTIATING;
  14. return cache[serviceName] = factory(serviceName, caller);
  15. } catch (err) {
  16. if (cache[serviceName] === INSTANTIATING) {
  17. delete cache[serviceName];
  18. }
  19. throw err;
  20. } finally {
  21. path.shift();
  22. }
  23. }
  24. }
  25.  
  26. function invoke(fn, self, locals, serviceName) {
  27. if (typeof locals === 'string') {
  28. serviceName = locals;
  29. locals = null;
  30. }
  31.  
  32. var args = [],
  33. // 解析并获取注入服务列表
  34. $inject = annotate(fn, strictDi, serviceName),
  35. length, i,
  36. key;
  37.  
  38. for (i = 0, length = $inject.length; i < length; i++) {
  39. key = $inject[i];
  40. if (typeof key !== 'string') {
  41. throw $injectorMinErr('itkn',
  42. 'Incorrect injection token! Expected service name as string, got {0}', key);
  43. }
  44. // 注入的服务作为参数传入
  45. args.push(
  46. locals && locals.hasOwnProperty(key)
  47. ? locals[key]
  48. : getService(key, serviceName)
  49. );
  50. }
  51. if (isArray(fn)) {
  52. fn = fn[length];
  53. }
  54.  
  55. // http://jsperf.com/angularjs-invoke-apply-vs-switch
  56. // #5388
  57. return fn.apply(self, args);
  58. }
  59.  
  60. function instantiate(Type, locals, serviceName) {
  61. // Check if Type is annotated and use just the given function at n-1 as parameter
  62. // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
  63. // Object creation: http://jsperf.com/create-constructor/2
  64. var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
  65. var returnedValue = invoke(Type, instance, locals, serviceName);
  66.  
  67. return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
  68. }
  69.  
  70. return {
  71. invoke: invoke,
  72. instantiate: instantiate,
  73. get: getService,
  74. annotate: annotate,
  75. has: function(name) {
  76. return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
  77. }
  78. };
  79. }

createInternalInjector方法创建$injector对象,传递的参数分别为缓存对象和工厂函数。在具体实现中,AngularJS创建了两个injector对象--providerInjector和instanceInjector(这两个对象的不同主要是createInternalInjector方法传递的缓存对象不同),而通过angular.injector()导出的就是instanceInjector。对于providerInjector,主要用来获取服务的提供者,即serviceProvider。而对于instanceInjector而言,主要用于执行从providerInjector获取的provider对象的$get方法,生产服务对象(依赖),并将服务对象传递给相应的函数,完成IoC。

首先从get方法说起,get方法主要获取指定名称的服务,通过angular的injector方法获取的是instanceInjector,而当缓存中没有该服务对象(依赖)时,我们需要执行factory(serviceName, caller)方法,我们看看对于的factory函数:

  1. instanceInjector = (instanceCache.$injector =
  2. createInternalInjector(instanceCache, function

(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return

  1. instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
  2. }));

红色部分即为factory函数,它显示通过providerInjector获取相应服务的提供者serviceProvider,然后调用instanceInjector的invoke方法在serviceProvider上下文执行serviceProvider的$get方法,返回服务对象并保存在缓存中。这样,便完成了服务对象(依赖)的获取和缓存。

invoke方法也很简单,它的入参分别问fn, self, locals, serviceName,即要执行的函数,函数执行的上下文,提供的options选项和服务名。首先获取函数的所有依赖名,通过annotate方法完成之后,如果options中提供了对于名称的依赖,则使用,否则通过get方法获取依赖,最后传入函数,并将函数的执行结果返回。invoke返回的结果往往是一个服务对象。

instantiate方法主要根据提供的构造函数创建一个示例,用作依赖或提供服务。值得一提的是并没有通过new关键字创建对象,而是通过ECMA5提供的Object.create来继承函数的原型对象实现,非常巧妙。

has方法则是相继判断serviceProvider和service是否存在于缓存中。

至此,$injector对象创建完毕。

注册服务(依赖)

服务不可能凭空而来,我们需要自己实现或者外部引入服务或依赖。所以,注册服务的模块也是值得深究的。AngularJS提供了多种注册服务的API,但是我们着重关注的是provider方法,其他factory,service方法都是基于此进行构建的。

这些方法(provider,factory等)绑定在providerCache.$provide对象上,而我们通过angular.module('app',[]).provider(...)方式调用的provider函数,会在module加载期间将调用(该调用抽象成一个数组,即[provider,method,arguments])绑定在内部的_invokeQueue数组中,最终在providerCache.$provide对象上调用provider方法,其他的controller,directive等方法类似,不过是绑定在providerCache.$controllerProvider,providerCache.$compileProvider对象上。

  1. function provider(name, provider_) {
  2. assertNotHasOwnProperty(name, 'service');
  3. if (isFunction(provider_) || isArray(provider_)) {
  4. provider_ = providerInjector.instantiate(provider_);
  5. }
  6. if (!provider_.$get) {
  7. throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
  8. }
  9. return providerCache[name + providerSuffix] = provider_;
  10. }
  11.  
  12. function enforceReturnValue(name, factory) {
  13. return function enforcedReturnValue() {
  14. var result = instanceInjector.invoke(factory, this);
  15. if (isUndefined(result)) {
  16. throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
  17. }
  18. return result;
  19. };
  20. }
  21.  
  22. function factory(name, factoryFn, enforce) {
  23. return provider(name, {
  24. $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
  25. });
  26. }
  27.  
  28. function service(name, constructor) {
  29. return factory(name, ['$injector', function($injector) {
  30. return $injector.instantiate(constructor);
  31. }]);
  32. }
  33.  
  34. function value(name, val) { return factory(name, valueFn(val), false); }
  35.  
  36. function constant(name, value) {
  37. assertNotHasOwnProperty(name, 'constant');
  38. providerCache[name] = value;
  39. instanceCache[name] = value;
  40. }
  41. // 在服务实例化之后进行调用(拦截),拦截函数中注入实例化的服务,可以修改,扩展替换服务。
  42. function decorator(serviceName, decorFn) {
  43. var origProvider = providerInjector.get(serviceName + providerSuffix),
  44. orig$get = origProvider.$get;
  45.  
  46. origProvider.$get = function() {
  47. var origInstance = instanceInjector.invoke(orig$get, origProvider);
  48. return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
  49. };
  50. }

provider方法需要两个参数,一个是服务名(依赖名),另外是工厂方法或者是一个包含依赖和工厂方法的数组。首先通过providerInjector创建工厂方法的一个实例,并添加到providerCache中,返回。

factory方法只是将第二个参数封装成了一个包含$get方法的对象,即serviceProvider,缓存。并不复杂。

而service方法则嵌套注入了$injector服务,即instanceInjector,它会创建构造函数的实例,作为服务对象。

value方法仅仅封装了一个provider,其$get方法返回value值。

constant方法则将value的值分别存入providerCache和instanceCache中,并不需要invoke获取其value值。

而比较特殊且扩展性较高的decorator方法,是在serviceProvider的$get方法后面添加一个拦截函数,并通过传递依赖$delegate来获取原先invoke $get方法返回的服务对象。我们可以通过decorator来对服务进行扩展,删除等操作。

流程

最后,在基本的实现已经完成的基础上,我们走一遍具体的注入流程,更易于我们的深入理解。

  1. angular.module("app",[])
  2. .provider("locationService",function(){
  3. ...
  4. })
  5. .controller("WeatherController",function($scope,locationService,$location){
  6. locationService.getWeather()
  7. .then(function(data){
  8. $scope.weather = data;
  9. },function(e){
  10. console.log("error message: "+ e.message)
  11. });
  12. })

我们不关心具体的代码实现,仅仅使用上述代码作为演示。

首先确定AngularJS上下文的范围,并且获取依赖模块(在此处为空);

继续注册服务(依赖),将serviceProvider缓存至providerCache中;

声明控制器;

在此获取$injector示例,通过执行invoke函数,获取[“$scope”,”locationService”,”$location”]依赖列表,通过$injector的get方法获取相应的依赖对象。对于$scope和$location服务而言,在AngularJS初始化时已经注入到Angular中,因此可以获取相应的provider对象,执行相关的方法返回$scope和$location对象,而locationService则在provider中进行了声明,因此获取到locationServiceProvider对象,通过调用instanceInjector.invoke(locationServiceProvider.$get, locationServiceProvider, undefined, “locationService”)返回locationService对象。

最后将所有的依赖组装成数组[$scope,locationService,$location]作为参数传递给匿名函数执行。

至此,依赖注入完成。

AngularJS源码分析之依赖注入$injector的更多相关文章

  1. angularjs源码分析之:angularjs执行流程

    angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directive,controller,service,compile,l ...

  2. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  3. AngularJS 源码分析1

    AngularJS简介 angularjs 是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里 再贴上一个 ...

  4. Monkey源码分析之事件注入

    本系列的上一篇文章<Monkey源码分析之事件源>中我们描述了monkey是怎么从事件源取得命令,然后将命令转换成事件放到事件队列里面的,但是到现在位置我们还没有了解monkey里面的事件 ...

  5. [Abp vNext 源码分析] - 3. 依赖注入与拦截器

    一.简要说明 ABP vNext 框架在使用依赖注入服务的时候,是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包.这里与原来的 ABP 框架 ...

  6. AngularJS 源码分析2

    上一篇地址 本文主要分析RootScopeProvider和ParseProvider RootScopeProvider简介 今天这个rootscope可是angularjs里面比较活跃的一个pro ...

  7. AngularJS 源码分析3

    本文接着上一篇讲 上一篇地址 回顾 上次说到了rootScope里的$watch方法中的解析监控表达式,即而引出了对parse的分析,今天我们接着这里继续挖代码. $watch续 先上一块$watch ...

  8. [WPF源码分析]ContentControl依赖项属性的双向绑定,two-way binding view's DependencyProperty and ViewModel's variable

    问题:自定义控件的依赖项属性和VIewModel中的变量不能双向绑定 解决思路:对比.net源码 PresentationFramework  /   System.Windows.Controls ...

  9. [Abp vNext 源码分析] - 文章目录

    一.简要介绍 ABP vNext 是 ABP 框架作者所发起的新项目,截止目前 (2019 年 2 月 18 日) 已经拥有 1400 多个 Star,最新版本号为 v 0.16.0 ,但还属于预览版 ...

随机推荐

  1. Android 网络状态检测

    package com.example.administrator.yunstore.net; import android.app.AlertDialog; import android.conte ...

  2. finally回收资源

    Java中的垃圾回收机制,也就是GC不会回收任何物理资源,垃圾回收机制只回收堆内存中对象所占用的内存,所以其他的物理资源需要用finally来回收. 如果try块中的某条语句引起了异常,该异常就会被c ...

  3. angular学习之路(一)

    angular是什么? angular是一个用于设计动态web应用的结构框架! 它不仅仅是一个JavaScript框架,他的核心其实是对HTML标签的增强. 何为HTML标签的增强?其实就是使用标签完 ...

  4. POOL_TYPE enumeration

    typedef enum _POOL_TYPE { NonPagedPool, NonPagedPoolExecute                   = NonPagedPool, PagedP ...

  5. matlab 采样函数

    dyaddown 功能:对时间序列进行二元采样,每隔一个元素提取一个元素,得到一个降采样时间序列. 格式: 1.y = dyaddown(x, EVENODD) 当EVENODD=0时,从x中第二个元 ...

  6. 多种坐标系之间的转换 Proj.NET和DotSpatial

    Proj.NET ( http://projnet.codeplex.com/)是一个.NET下开源的空间参照和投影引擎,遵循OGC相关标准.负责人(Coordinators )是D_Guidi 和S ...

  7. CI-持续集成(1)-软件工业“流水线”概述

    CI-持续集成(1)-软件工业“流水线”概述 1   概述 持续集成(Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次, ...

  8. Guava monitor

    Guava的com.google.util.concurrent类库提供了相对于jdk java.util.concurrent包更加方便实用的并发类,Monitor类就是其中一个.Monitor类在 ...

  9. LOMA280保险原理读书笔记

    LOMA是国际金融保险管理学院(Life Office Management Association)的英文简称.国际金融保险管理学院是一个保险和金融服务机构的国际组织,它的创建目的是为了促进信息交流 ...

  10. 使用GDB调试Go语言

    用Go语言已经有一段时间了,总结一下如何用GDB来调试它! ps:网上有很多文章都有描述,但是都不是很全面,这里将那些方法汇总一下 GDB简介  GDB是GNU开源组织发布的⼀一个强⼤大的UNIX下的 ...