上一篇文章初次介绍了注入器(Injector),分析了它加载模块的过程以及它是如何执行任务队列的。这里需要重申一下的是,所谓任务队列实际上就是我们在开发一个基于angular的应用时定义的那些constantservicefactory等等,它们通过module类型提供的方法定义,但是定义并不代表立即就创建。它们的创建工作是交给注入器来完成的。

那么执行了这些任务后,会产生什么效果,这又和我们讨论的主题-依赖注入有什么关联呢?这就是我们在这篇文章中需要讨论的。

依赖注入总览

在继续讨论之前,我们需要看看依赖注入到底在angular应用中意味着什么。众所周知,在开发一个angular应用时,我们可以直接将需要的服务以参数的形式定义在所定义的各种angular提供的类型中,比如controllerservicefactory等等,下面是一段典型代码:

angular.module('test').controller('testController', function($rootScope, testConstant, testFactory) {// 利用$rootScope, testConstant, testFactory完成业务逻辑
}); 

在感受到这种业务代码编写方式便利性的时候,你有没有感觉到哪怕是一丝半毫的不可思议呢?我们需要的各种服务和数据为什么就能够以这种直接定义成参数的形式得以实现呢?

angular框架是如何得知上述参数中定义的$rootScope, testConstant, testFactory是从何而来呢?如果我们把它们换个名字,比如换成$rootScope1, testConstant1, testFactory1,还能不能得到相同的结果呢?

带着这些问题,我们来看看依赖注入是如何解决这个问题的。所谓依赖注入,实际上是控制反转(Inverse of Control)设计思想的一种具体模式。而控制反转的核心思想等同于所谓的”好莱坞原则”:”不要打电话给我们,我们会打电话给你”。套用在依赖注入的上下文中,这句话就演绎成了:”不要去主动寻找和创建需要的服务,给个名字交给注入器帮你搞定”。因此在这个前提条件下,才有了我们在前述代码中所看到的那样,我们在参数列表中声明了我们需要的服务和数据,注入器真的就帮我们搞定了。而且上述controller定义的function并不是由应用程序来调用,它是通过angular框架进行调用,这样才能够将真正的参数传入进去。从这个角度而言,它也实践了控制反转这一原则。作为应用程序开发者的你,只需要根据规范定义好业务逻辑即可,后续的一切工作全部交给框架处理。

弄明白了这个问题,剩下的问题就变成了:注入器是如何搞定的呢?我们只提供了一个名字,它就搞定了?感觉很神奇吧,其实我们在前面已经给出了这个问题的答案 — 任务队列。

执行任务队列的目的

执行任务队列的目的有两点:
1. 为了创建这些定义的数据(比如简单的一点的constantvalue等)。这类数据往往比较简单,即使直接创建出来也占用不了太多资源。
2. 为了提供给注入器如何创建服务的”蓝图”(比如复杂一点的如controllerservicefactory等),这样做的目的是为了实现”懒加载”,因为这类对象往往会比较复杂,如果在注入器在加载模块的时候就一股脑地将它们全部给创建出来了,然而应用中却没有使用它们,岂不是很亏?

注入器如何管理对象

然后在需要某个对象的时候,注入器会首先来看它是不是已经存在了。如果存在的话就直接返回,如果不存在就会先创建,然后保存到缓存中并返回。相关代码如下所示:

// 所有被注入器管理的对象缓存
var instanceCache = {};// 获取服务的函数,函数体中的cache就是上面的instanceCache
function getService(serviceName, caller) {// 检查缓存中是否已经存在需要的服务if (cache.hasOwnProperty(serviceName)) {// 如果发现该服务已经被标注为"正在实例化",则抛出循环依赖异常if (cache[serviceName] === INSTANTIATING) {throw $injectorMinErr('cdep', 'Circular dependency found: {0}',serviceName + ' <- ' + path.join(' <- '));}return cache[serviceName];} else {try {// 将服务名置入到path数组中,记录实例化服务的顺序path.unshift(serviceName);// 将服务标注为"正在实例化"cache[serviceName] = INSTANTIATING;// 调用factory来实例化得到服务对象 --- 此时才真正得到了服务对象return cache[serviceName] = factory(serviceName, caller);} catch (err) {// 发生异常时,清除实例化出错的服务if (cache[serviceName] === INSTANTIATING) {delete cache[serviceName];}throw err;} finally {// 清除最后的路径信息path.shift();}}
}

如注释所解释的那样,以上的代码主要干了这么几件事情:
1. 判断是否发生循环依赖,如果发生了是要抛出异常的
2. 被注入器托管对象的管理 — 创建和获取


循环依赖的检测

对于第一点,循环依赖的问题。相信有一些实际angular开发经验的同学们一定已经遇到过了。下面这段代码重现了这个问题:

<html ng-app="test">
<head><title>Angular Circular Dependency Example</title>
</head>
<body ng-controller="testController">Test
</body>
<script src="//cdn.bootcss.com/angular.js/1.5.8/angular.js"></script>
<script type="text/javascript">var module = angular.module('test', []);module.service('service1', function(service2) {});module.service('service2', function(service1) {});module.controller('testController', function(service1) {});
</script>
</html>

上面定义的两个service互相依赖于对方。但是根据注入器”懒加载”的特性,如果仅仅定义了两个service而不定义在哪使用它们的话,也是不会触发注入器的实例化操作的。因此还定义了一个controller,并在body元素上声明了使用该控制器,用来触发注入器实例化的行为,进而触发我们所期待的循环依赖异常。

如果运行这个例子就会出现下面的异常:

angular.js:13920 Error: [$injector:cdep] Circular dependency found: service1 <- service2 <- service1
http://errors.angularjs.org/1.5.8/$injector/cdep?p0=service1%20%3C-%20service2%20%3C-%20service1at angular.js:68at getService (angular.js:4656)at injectionArgs (angular.js:4688)at Object.instantiate (angular.js:4730)at Object.<anonymous> (angular.js:4573)at Object.invoke (angular.js:4718)at Object.enforcedReturnValue [as $get] (angular.js:4557)at Object.invoke (angular.js:4718)at angular.js:4517at getService (angular.js:4664)

值得一提的是,上面的示例程序中使用的是未经过压缩混淆的angular源代码。如果你使用的是压缩混淆过的angular.min.js。输出就不会这么详尽了:

Error: [$injector:cdep] http://errors.angularjs.org/1.5.8/$injector/cdep?p0=service1%20%3C-%20service2%20%3C-%20service1at Error (native)at http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:6:412at d (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:40:349)at e (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:41:158)at Object.instantiate (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:42:24)at Object.<anonymous> (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:42:352)at Object.invoke (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:41:456)at Object.$get (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:39:142)at Object.invoke (http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:41:456)at http://cdn.bootcss.com/angular.js/1.5.8/angular.min.js:43:265

大家可以对比一下输出的不同。除了调用栈不同之外,前者还多了循环依赖的详细信息:

Circular dependency found: service1 <- service2 <- service1

而以上循环依赖的数据来源正是path数组。因此,在开发一个angular应用的时候,也建议大家使用angular.js,而非angular.min.js。因为在发生异常时前者能够提供更多的信息。关于angular异常的封装,可以参考我的另外一篇文章,对这个问题进行了探讨。

发生循环依赖的本质还是在于注入器在实例化服务对象的时候,采用的算法也是深度优先遍历,这一点在原理上和注入器处理模块的加载是别无二致的。因为一个服务对象也可能需要首先依赖更多的其它服务对象,这样逐层深入下去,就成了一张服务对象依赖关系图。这张单向图需要是一张有向无环图(DAG),否则就无法解释到底是谁依赖谁了。因此,这也算是拓扑排序算法在angular中的一个简单应用吧。关于拓扑排序,有兴趣的同学还可以看这篇文章,欢迎大家来探讨。


被托管对象的创建和获取

关于第二点,注入器对被托管对象的创建和获取。
就创建而言,分为简单对象和复杂对象。像诸如constantvalue这样的简单对象,直接定义到缓存中即可。对于复杂对象,调用通过执行任务队列得到的”蓝图”即可,也就是getService方法中的factory函数。就获取而言,它和创建其实是相辅相成的,创建了之后才能获取。

结语

本篇文章介绍了依赖注入的原理,以及angular是如何实践这一原理的。当然,这还没完。现在介绍的只是angular的注入器是如何管理被托管对象的,离实际应用还差了一点料。这个料就是下一篇文章的主题 — 注解的定义与实现。正是这个料最终促成了angular中依赖注入服务$injector的诞生。

[AngularJS面面观] 16. 依赖注入 --- 注入器中如何管理对象相关推荐

  1. AngularJs 基础教程 —— 依赖注入

    为什么80%的码农都做不了架构师?>>>    本文为 H5EDU 机构官方 HTML5培训 教程,主要介绍:AngularJs 基础教程 -- 依赖注入 AngularJS 依赖注 ...

  2. 依赖注入模式中,为什么用对象而不是用数组传递?

    依赖注入(Dependence Injection, DI) 依赖注入是控制反转的一种设计模式.依赖注入的核心是把类所依赖的单元的实例化过程,放到类的外面去实现.依赖注入的实现离不开反射. 依赖注入( ...

  3. ASP.NET Core依赖注入容器中的动态服务注册

    介绍 在ASP.NET Core中,每当我们将服务作为依赖项注入时,都必须将此服务注册到ASP.NET Core依赖项注入容器.但是,一个接一个地注册服务不仅繁琐且耗时,而且容易出错.因此,在这里,我 ...

  4. 如何检查服务已在依赖注入容器中注册

    前言 依赖关系注入(DI),是一种在类及其依赖项之间实现控制反转(IoC)的技术.在ASP.NET Core中,依赖关系注入是"一等公民",被大量使用. 但是有时,我们仅仅只需要知 ...

  5. Angular 中的依赖注入link

    Angular 中的依赖注入link 依赖注入(DI)是一种重要的应用设计模式. Angular 有自己的 DI 框架,在设计应用时常会用到它,以提升它们的开发效率和模块化程度. 依赖,是当类需要执行 ...

  6. angular 注入器配置_Angular依赖注入介绍

    依赖注入(DI -- Dependency Injection)是一种重要的应用设计模式.Angular里面也有自己的DI框架,在设计应用时经常会用到它,它可以我们的开发效率和模块化程度. 依赖,是当 ...

  7. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )

    文章目录 前言 一.获取 Activity 中的所有方法 二.获取方法上的注解 三.获取注解上的注解 四.通过注解属性获取相关事件信息 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  8. .NET 中依赖注入组件 Autofac 的性能漫聊

    Autofac 是一款超赞的 .NET IoC 容器 ,在众多性能测评中,它也是表现最优秀的一个.它管理类之间的依赖关系, 从而使 应用在规模及复杂性增长的情况下依然可以轻易地修改.它的实现方式是将常 ...

  9. .NET Core中的一个接口多种实现的依赖注入与动态选择

    最近有个需求就是一个抽象仓储层接口方法需要SqlServer以及Oracle两种实现方式,为了灵活我在依赖注入的时候把这两种实现都给注入进了依赖注入容器中,但是在服务调用的时候总是获取到最后注入的那个 ...

  10. C#:在Task中使用依赖注入的Service/EFContext

    dotnet core时代,依赖注入基本已经成为标配了,这就不多说了. 前几天在做某个功能的时候遇到在Task中使用EF DbContext的问题,学艺不精的我被困扰了不短的一段时间,于是有了这个文章 ...

最新文章

  1. linux敏感目录文件,Windows系统和Linux系统常见敏感信息路径
  2. JAVA中向量类Vector
  3. Python新手学习基础之数据类型——字符串的切片截取
  4. 每周荐书:大数据、深度学习、架构(评论送书)
  5. LeetCode 148 排序链表
  6. Linux-虚拟机封装
  7. python 魔兽世界钓鱼_有关魔兽世界怀旧服的钓鱼工具的一点思考
  8. 小程序 字号设置 slider滚动改变大小_Snipaste(滚动截图软件)app下载|Snipaste(滚动截图软件) 1.15.2 绿色版(32/64位)...
  9. 离线使用yum·无法使用yum的情况下安装软件·最简单的方法
  10. 绝对免费搭建不限速私人网盘5T存储空间:Gearhost免费空间+OneIndex程序+Onedrive免费账号
  11. 编程语言的宗教狂热和十字军东征
  12. 深入理解Java虚拟机到底是什么
  13. 哈工大计算机系统Lab4.Tiny Shell
  14. Java通过SMS短信平台实现发短信功能
  15. (Python)从零开始,简单快速学机器仿人视觉Opencv---运用二:物体检测
  16. Git 版本控制工具学习
  17. 【算法记录】梅式砝码问题
  18. 如何进行自动化测试?提高测试效率,缩短开发周期。
  19. 笔记本摄像头打开自动灭的问题解决(win10)
  20. el-table封装,elementtable封装,eltable封装详解(含分页)

热门文章

  1. Python工作任务自动化教程
  2. 518抽奖软件教程之:公司、单位年会抽奖
  3. java抓取豆瓣网页内容_爬虫实践--豆瓣电影当前上映电影信息爬取
  4. 将pip源更换到国内镜像,如清华源,阿里源等
  5. MATLAB地图作为底图,Matlab中自带地图绘制WorldMap详解
  6. 工作流引擎 Activiti 教程(非常详细)
  7. 龙果 mycat mysql_龙果学院Spring Boot源码解析视频教程完整未加密(价值599)
  8. html 苹果手机输入法,苹果手机搜狗输入法怎么计算字数?
  9. pdf文档怎样转换成word文档?2022pdf转word软件推荐
  10. MUI框架-01-介绍-准备-创建项目