服务容器的绑定

bind 绑定

bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种:

  • 绑定自身

  • 绑定闭包

  • 绑定接口

今天,我们这篇文章主要从源码上讲解 Ioc 服务容器是如何进行绑定的。

  1. /**
  2. * Register a binding with the container.
  3. *
  4. * @param string|array $abstract
  5. * @param \Closure|string|null $concrete
  6. * @param bool $shared
  7. * @return void
  8. */
  9. public function bind($abstract, $concrete = null, $shared = false)
  10. {
  11. // If no concrete type was given, we will simply set the concrete type to the
  12. // abstract type. After that, the concrete type to be registered as shared
  13. // without being forced to state their classes in both of the parameters.
  14. $this->dropStaleInstances($abstract);
  15.  
  16. if (is_null($concrete)) {
  17. $concrete = $abstract;
  18. }
  19.  
  20. // If the factory is not a Closure, it means it is just a class name which is
  21. // bound into this container to the abstract type and we will just wrap it
  22. // up inside its own Closure to give us more convenience when extending.
  23. if (! $concrete instanceof Closure) {
  24. $concrete = $this->getClosure($abstract, $concrete);
  25. }
  26.  
  27. $this->bindings[$abstract] = compact('concrete', 'shared');
  28.  
  29. // If the abstract type was already resolved in this container we'll fire the
  30. // rebound listener so that any objects which have already gotten resolved
  31. // can have their copy of the object updated via the listener callbacks.
  32. if ($this->resolved($abstract)) {
  33. $this->rebound($abstract);
  34. }
  35. }

  

从源码中我们可以看出,服务器的绑定有如下几个步骤:

  1. 去除原有注册。去除当前绑定接口的原有实现单例对象,和原有的别名,为实现绑定新的实现做准备。

  2. 加装闭包。如果实现类不是闭包(绑定自身或者绑定接口),那么就创建闭包,以实现 lazy 加载。

  3. 注册。将闭包函数和单例变量存入 bindings 数组中,以备解析时使用。

  4. 回调。如果绑定的接口已经被解析过了,将会调用回调函数,对已经解析过的对象进行调整。

去除原有注册

dropStaleInstances 用于去除当前接口原有的注册和别名,这里负责清除绑定的 aliases 和单例对象的 instances,bindings 后面再做修改:

  1. protected function dropStaleInstances($abstract)
  2. {
  3. unset($this->instances[$abstract], $this->aliases[$abstract]);
  4. }

  

加装闭包

getClosure 的作用是为注册的非闭包实现外加闭包,这样做有两个作用:

  • 延时加载

服务容器在 getClosure 中为每个绑定的类都包一层闭包,这样服务容器就只有进行解析的时候闭包才会真正进行运行,实现了 lazy 加载的功能。

  • 递归绑定

对于服务容器来说,绑定是可以递归的,例如:

  1. $app->bind(A::class,B::class);
  2. $app->bind(B::class,C::class);
  3. $app->bind(C::class,function(){
  4. return new C;
  5. })

  

对于 A 类,我们直接解析 A 可以得到 B 类,但是如果仅仅到此为止,服务容器直接去用反射去创建 B 类的话,那么就很有可能创建失败,因为 B 类很有可能也是接口,B 接口绑定了其他实现类,要知道接口是无法实例化的。

因此服务容器需要递归地对 A 进行解析,这个就是 getClosure 的作用,它把所有可能会递归的绑定在闭包中都用 make 函数,这样解析 make(A::class) 的时候得到闭包 make(B::class),make(B::class) 的时候会得到闭包 make(C::class),make(C::class) 终于可以得到真正的实现了。

对于自我绑定的情况,因为不存在递归情况,所以在闭包中会使用 build 函数直接创建对象。(如果仍然使用 make,那就无限循环了)

  1. protected function getClosure($abstract, $concrete)
  2. {
  3. return function ($container, $parameters = []) use ($abstract, $concrete) {
  4. if ($abstract == $concrete) {
  5. return $container->build($concrete);
  6. }
  7. return $container->makeWith($concrete, $parameters);
  8. };
  9. }

  

注册

注册就是向 binding 数组中添加注册的接口与它的实现,其中 compact() 函数创建包含变量名和它们的值的数组,创建后的结果为:

  1. $bindings[$abstract] = [
  2. 'concrete' => $concrete,
  3. 'shared' => $shared
  4. ]

  

回调

注册之后,还要查看当前注册的接口是否已经被实例化,如果已经被服务容器实例化过,那么就要调用回调函数。(若存在回调函数)
resolved() 函数用于判断当前接口是否曾被解析过,在判断之前,先获取了接口的最终服务名:

  1. public function resolved($abstract)
  2. {
  3. if ($this->isAlias($abstract)) {
  4. $abstract = $this->getAlias($abstract);
  5. }
  6.  
  7. return isset($this->resolved[$abstract]) ||
  8. isset($this->instances[$abstract]);
  9. }
  10.  
  11. public function isAlias($name)
  12. {
  13. return isset($this->aliases[$name]);
  14. }

  

getAlias() 函数利用递归的方法获取别名的最终服务名称:

  1. public function getAlias($abstract)
  2. {
  3. if (! isset($this->aliases[$abstract])) {
  4. return $abstract;
  5. }
  6.  
  7. if ($this->aliases[$abstract] === $abstract) {
  8. throw new LogicException("[{$abstract}] is aliased to itself.");
  9. }
  10.  
  11. return $this->getAlias($this->aliases[$abstract]);
  12. }

  

如果当前接口已经被解析过了,那么就要运行回调函数:

  1. protected function rebound($abstract)
  2. {
  3. $instance = $this->make($abstract);
  4.  
  5. foreach ($this->getReboundCallbacks($abstract) as $callback) {
  6. call_user_func($callback, $this, $instance);
  7. }
  8. }
  9.  
  10. protected function getReboundCallbacks($abstract)
  11. {
  12. if (isset($this->reboundCallbacks[$abstract])) {
  13. return $this->reboundCallbacks[$abstract];
  14. }
  15.  
  16. return [];
  17. }

  

这里面的 reboundCallbacks 从哪里来呢?这就是 Laravel核心——Ioc服务容器 文章中提到的 rebinding

  1. public function rebinding($abstract, Closure $callback)
  2. {
  3. $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;
  4.  
  5. if ($this->bound($abstract)) {
  6. return $this->make($abstract);
  7. }
  8. }

  

值得注意的是: rebinding 函数不仅绑定了回调函数,同时顺带还对接口abstract进行了解析,因为只有解析过,下次注册才会调用回调函数。

singleton 绑定

  1. singleton 绑定仅仅是 bind 绑定的一个 shared 为真的形式:
  2.  
  3. public function singleton($abstract, $concrete = null)
  4. {
  5. $this->bind($abstract, $concrete, true);
  6. }

  

instance 绑定

不对接口进行解析,直接给接口一个实例作为单例对象。从下面可以看出,主要的工作就是去除接口在abstractAliases 数组和 aliases 数组中的痕迹,防止 make 函数根据别名继续解析下去出现错误。如果当前接口曾经注册过,那么就调用回调函数。

  1. public function instance($abstract, $instance)
  2. {
  3. $this->removeAbstractAlias($abstract);
  4.  
  5. $isBound = $this->bound($abstract);
  6.  
  7. unset($this->aliases[$abstract]);
  8.  
  9. $this->instances[$abstract] = $instance;
  10.  
  11. if ($isBound) {
  12. $this->rebound($abstract);
  13. }
  14. }
  15.  
  16. protected function removeAbstractAlias($searched)
  17. {
  18. if (! isset($this->aliases[$searched])) {
  19. return;
  20. }
  21.  
  22. foreach ($this->abstractAliases as $abstract => $aliases) {
  23. foreach ($aliases as $index => $alias) {
  24. if ($alias == $searched) {
  25. unset($this->abstractAliases[$abstract][$index]);
  26. }
  27. }
  28. }
  29. }
  30.  
  31. public function bound($abstract)
  32. {
  33. return isset($this->bindings[$abstract]) ||
  34. isset($this->instances[$abstract]) ||
  35. $this->isAlias($abstract);
  36. }

  

Context 绑定

Context 绑定一般用于依赖注入,当我们利用依赖注入来自动实例化对象时,服务容器其实是利用反射机制来为构造函数实例化它的参数,这个过程中,被实例化的对象就是下面的 concrete,构造函数的参数接口是 abstract,参数接口实际的实现是 implementation。
例如:

  1. $this->app->when(PhotoController::class)
  2. ->needs(Filesystem::class)
  3. ->give(function () {
  4. return Storage::disk('local');
  5. });

  

这里实例化对象 concrete 就是 PhotoController,构造函数的参数接口 abstract 就是 Filesystem。参数接口实际实现 implementation 是 Storage::disk('local')。

这样,每次进行解析构造函数的参数接口的时候,都会去判断当前的 contextual 数组里面 concrete[concrete] [abstract](也就是 concrete[PhotoController::class] [Filesystem::class])对应的上下文绑定,如果有就直接从数组中取出来,如果没有就按照正常方式解析。
值得注意的是,concrete 和 abstract 都是利用 getAlias 函数,保证最后拿到的不是别名。

  1. public function when($concrete)
  2. {
  3. return new ContextualBindingBuilder($this, $this->getAlias($concrete));
  4. }
  5. public function __construct(Container $container, $concrete)
  6. {
  7. $this->concrete = $concrete;
  8. $this->container = $container;
  9. }
  10. public function needs($abstract)
  11. {
  12. $this->needs = $abstract;
  13.  
  14. return $this;
  15. }
  16. public function give($implementation)
  17. {
  18. $this->container->addContextualBinding(
  19. $this->concrete, $this->needs, $implementation
  20. );
  21. }
  22. public function addContextualBinding($concrete, $abstract, $implementation)
  23. {
  24. $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
  25. }

  

tag 绑定

标签绑定比较简单,绑定过程就是将标签和接口之间建立一个对应数组,在解析的过程中,按照标签把所有接口都解析一遍即可。

  1. public function tag($abstracts, $tags)
  2. {
  3. $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);
  4.  
  5. foreach ($tags as $tag) {
  6. if (! isset($this->tags[$tag])) {
  7. $this->tags[$tag] = [];
  8. }
  9.  
  10. foreach ((array) $abstracts as $abstract) {
  11. $this->tags[$tag][] = $abstract;
  12. }
  13. }
  14. }

  

数组绑定

利用数组进行绑定的时候 ($app()[A::class] = B::class),服务容器会调用 offsetSet 函数:

  1. public function offsetSet($key, $value)
  2. {
  3. $this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
  4. return $value;
  5. });
  6. }

  

extend扩展

extend 扩展分为两种,一种是针对instance注册的对象,这种情况将立即起作用,并更新之前实例化的对象;另一种情况是非 instance 注册的对象,那么闭包函数将会被放入 extenders 数组中,将在下一次实例化对象的时候才起作用:

  1. public function extend($abstract, Closure $closure)
  2. {
  3. $abstract = $this->getAlias($abstract);
  4.  
  5. if (isset($this->instances[$abstract])) {
  6. $this->instances[$abstract] = $closure($this->instances[$abstract], $this);
  7.  
  8. $this->rebound($abstract);
  9. } else {
  10. $this->extenders[$abstract][] = $closure;
  11.  
  12. if ($this->resolved()) {
  13. $this->rebound($abstract);
  14. }
  15. }
  16. }

  

服务器事件

服务器的事件注册依靠 resolving 函数和 afterResolving 函数,这两个函数维护着 globalResolvingCallbacks、resolvingCallbacks、globalAfterResolvingCallbacks、afterResolvingCallbacks 数组,这些数组中存放着事件的回调闭包函数,每当对对象进行解析时就会遍历这些数组,触发事件:

  1. public function resolving($abstract, Closure $callback = null)
  2. {
  3. if (is_string($abstract)) {
  4. $abstract = $this->getAlias($abstract);
  5. }
  6.  
  7. if (is_null($callback) && $abstract instanceof Closure) {
  8. $this->globalResolvingCallbacks[] = $abstract;
  9. } else {
  10. $this->resolvingCallbacks[$abstract][] = $callback;
  11. }
  12. }
  13.  
  14. public function afterResolving($abstract, Closure $callback = null)
  15. {
  16. if (is_string($abstract)) {
  17. $abstract = $this->getAlias($abstract);
  18. }
  19.  
  20. if ($abstract instanceof Closure && is_null($callback)) {
  21. $this->globalAfterResolvingCallbacks[] = $abstract;
  22. } else {
  23. $this->afterResolvingCallbacks[$abstract][] = $callback;
  24. }
  25. }

  

本文转自:https://segmentfault.com/a/1190000009369494

Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)的更多相关文章

  1. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)

    make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...

  2. Laravel框架下路由的使用(源码解析)

    本篇文章给大家带来的内容是关于Laravel框架下路由的使用(源码解析),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言 我的解析文章并非深层次多领域的解析攻略.但是参考着开发文 ...

  3. Laravel开发:Laravel核心——Ioc服务容器

    服务容器 在说 Ioc 容器之前,我们需要了解什么是 Ioc 容器. Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具. 在理解这句话之前,我们需要先了解一下服务容器的来龙去脉:  ...

  4. Netty5服务端源码解析

    Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...

  5. SpringCloud服务调用源码解析汇总

    相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...

  6. 【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)

    我们前面的三篇博文,简单易懂的介绍了为什么要使用IOC[实例讲解](二).和Spring的IOC原理[通俗解释](三)以及依赖注入的两种常用实现类型(四),这些都是刚开始学习Spring IoC容器时 ...

  7. 【Spring】Spring IOC原理及源码解析之scope=request、session

    一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存 ...

  8. Android 玩转IOC,Retfotit源码解析,教你徒手实现自定义的Retrofit框架

    CSDN:码小白 原文地址: http://blog.csdn.net/sk719887916/article/details/51957819 前言 Retrofit用法和介绍的文章实在是多的数不清 ...

  9. nacos服务注册源码解析

    1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...

随机推荐

  1. kubernetes1.5.2集群部署过程--非安全模式

    运行环境 宿主机:CentOS7 7.3.1611 关闭selinux etcd 3.1.9 flunnel 0.7.1 docker 1.12.6 kubernetes 1.5.2 安装软件 yum ...

  2. JSONUtil.bean2Json()报Property 'key' of class has no read method. SKIPPED的问题处理

    错误警告信息描述: net.sf.json.JSONObject.defaultBeanProcessing(JSONObject.java:) Property 'handler' of class ...

  3. JS或jQuery获取当前屏幕宽度

    Javascript: 网页可见区域宽: document.body.clientWidth网页可见区域高: document.body.clientHeight网页可见区域宽: document.b ...

  4. elasticsearch 安装和部署

    jdk要用1.8以上(elasticsearch版本是1.7.3) 下载elasticsearch的tar包,解压开,更改其名称  mv elasticsearch-5.x.x elasticsear ...

  5. 通过Fsharp探索Enterprise Library Exception

    Exception怎么生成是一回事,怎么展示又是还有一回事了. Exception Block主要关注的点在于Exception信息的展示.Exception不同于一般的log信息,是系统设计者未考虑 ...

  6. Android JNI/NDK开发教程

    JNI/NDK开发指南:http://blog.csdn.net/xyang81/article/details/41759643

  7. 倍福TwinCAT(贝福Beckhoff)基础教程 松下伺服驱动器报错 24.0怎么办

    24.0 位置偏差过大保护   读取驱动器参数之后,在基本的014项目把设定值设置为最大,然后点击传送,EEP写入驱动器后重启驱动器即可     更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空 ...

  8. 从零单排之玩转Python安全编程(II)

    转自:http://www.secpulse.com/archives/35893.html 都说Python大法好,作为一名合格的安全从业人员,不会几门脚本语言都不好意思说自己是从事安全行业的. 而 ...

  9. Android学习(十二) ContentProvider

    一.ContentProvider简介       当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽然使用其他方法也可以对外共享数据, ...

  10. 未能从程序集“Oracle.ManagedDataAccess”加载 “OracleInternal.Common.ConfigBaseClass”

    使用VS2015做项目的过程中一直使用的服务器上的oracle数据库,后来想学习一下oracle,就在本机安装了oracle.可没想到本来运行好好的项目,现在不能运行了.项目是使用的Abp框架,当运行 ...