服务容器的绑定

bind 绑定

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

  • 绑定自身

  • 绑定闭包

  • 绑定接口

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

/**
* Register a binding with the container.
*
* @param string|array $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
$this->dropStaleInstances($abstract); if (is_null($concrete)) {
$concrete = $abstract;
} // If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
} $this->bindings[$abstract] = compact('concrete', 'shared'); // If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}

  

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

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

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

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

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

去除原有注册

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

protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}

  

加装闭包

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

  • 延时加载

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

  • 递归绑定

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

$app->bind(A::class,B::class);
$app->bind(B::class,C::class);
$app->bind(C::class,function(){
return new C;
})

  

对于 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,那就无限循环了)

protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->makeWith($concrete, $parameters);
};
}

  

注册

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

$bindings[$abstract] = [
'concrete' => $concrete,
'shared' => $shared
]

  

回调

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

public function resolved($abstract)
{
if ($this->isAlias($abstract)) {
$abstract = $this->getAlias($abstract);
} return isset($this->resolved[$abstract]) ||
isset($this->instances[$abstract]);
} public function isAlias($name)
{
return isset($this->aliases[$name]);
}

  

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

public function getAlias($abstract)
{
if (! isset($this->aliases[$abstract])) {
return $abstract;
} if ($this->aliases[$abstract] === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
} return $this->getAlias($this->aliases[$abstract]);
}

  

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

protected function rebound($abstract)
{
$instance = $this->make($abstract); foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
} protected function getReboundCallbacks($abstract)
{
if (isset($this->reboundCallbacks[$abstract])) {
return $this->reboundCallbacks[$abstract];
} return [];
}

  

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

public function rebinding($abstract, Closure $callback)
{
$this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; if ($this->bound($abstract)) {
return $this->make($abstract);
}
}

  

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

singleton 绑定

singleton 绑定仅仅是 bind 绑定的一个 shared 为真的形式:

public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}

  

instance 绑定

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

public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract); $isBound = $this->bound($abstract); unset($this->aliases[$abstract]); $this->instances[$abstract] = $instance; if ($isBound) {
$this->rebound($abstract);
}
} protected function removeAbstractAlias($searched)
{
if (! isset($this->aliases[$searched])) {
return;
} foreach ($this->abstractAliases as $abstract => $aliases) {
foreach ($aliases as $index => $alias) {
if ($alias == $searched) {
unset($this->abstractAliases[$abstract][$index]);
}
}
}
} public function bound($abstract)
{
return isset($this->bindings[$abstract]) ||
isset($this->instances[$abstract]) ||
$this->isAlias($abstract);
}

  

Context 绑定

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

$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});

  

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

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

public function when($concrete)
{
return new ContextualBindingBuilder($this, $this->getAlias($concrete));
}
public function __construct(Container $container, $concrete)
{
$this->concrete = $concrete;
$this->container = $container;
}
public function needs($abstract)
{
$this->needs = $abstract; return $this;
}
public function give($implementation)
{
$this->container->addContextualBinding(
$this->concrete, $this->needs, $implementation
);
}
public function addContextualBinding($concrete, $abstract, $implementation)
{
$this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
}

  

tag 绑定

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

public function tag($abstracts, $tags)
{
$tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); foreach ($tags as $tag) {
if (! isset($this->tags[$tag])) {
$this->tags[$tag] = [];
} foreach ((array) $abstracts as $abstract) {
$this->tags[$tag][] = $abstract;
}
}
}

  

数组绑定

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

public function offsetSet($key, $value)
{
$this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
return $value;
});
}

  

extend扩展

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

public function extend($abstract, Closure $closure)
{
$abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract);
} else {
$this->extenders[$abstract][] = $closure; if ($this->resolved()) {
$this->rebound($abstract);
}
}
}

  

服务器事件

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

 public function resolving($abstract, Closure $callback = null)
{
if (is_string($abstract)) {
$abstract = $this->getAlias($abstract);
} if (is_null($callback) && $abstract instanceof Closure) {
$this->globalResolvingCallbacks[] = $abstract;
} else {
$this->resolvingCallbacks[$abstract][] = $callback;
}
} public function afterResolving($abstract, Closure $callback = null)
{
if (is_string($abstract)) {
$abstract = $this->getAlias($abstract);
} if ($abstract instanceof Closure && is_null($callback)) {
$this->globalAfterResolvingCallbacks[] = $abstract;
} else {
$this->afterResolvingCallbacks[$abstract][] = $callback;
}
}

  

本文转自: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. Creating and Flashing UBIFS with MTD Utils

    转:http://wiki.atlas-embedded.com/index.php?title=Creating_and_Flashing_UBIFS_with_MTD_Utils Contents ...

  2. SparkSQL的3种Join实现

    引言 Join是SQL语句中的常用操作,良好的表结构能够将数据分散在不同的表中,使其符合某种范式,减少表冗余.更新容错等.而建立表和表之间关系的最佳方式就是Join操作. 对于Spark来说有3中Jo ...

  3. MVC流程图

    MVC请求流程图 一 MVC流程图 二 流程步骤 客户端浏览器发送请求到MVC应用程序. Global.ascx接收这个请求,并且执行基于使用RouteTable,RouteData,UrlRouti ...

  4. 解决https协议服务器内部无法跳转的问题

    <!-- 定义视图文件解析{视图解析器} --> <bean class="org.springframework.web.servlet.view.InternalRes ...

  5. [Angular] Debug Angular apps in production without revealing source maps

    Source: https://blog.angularindepth.com/debug-angular-apps-in-production-without-revealing-source-ma ...

  6. 倒计时:CountDownLatch(火箭发射前的准备)读书笔记

     这是一个非常实用的多线程控制工具类,经典的场景就是 火箭发射,在火箭发射前,为了保证万无一失,往往还要进行各项设备,仪器的检查,只有等待所有的检查完毕后,引擎才能点火,      CountDown ...

  7. 国内外DNS服务器地址列表大全

    DNS(Domain Name System)是域名解析服务器的意思,它在互联网的作用是把域名转换成为网络可以识别的IP地址. 通常来说,香港.韩国.日本等国的DNS服务器速度会比较快,大家可以多用几 ...

  8. react-native 启动页(react-native-splash-screen)

    用于解决iOS和Android启动白屏问题及简单的启动页面展示 下载 react-native-splash-screen yarn add react-native-splash-screen re ...

  9. jq:jq开头为什么那么写

    转自:http://holysonll.blog.163.com/blog/static/2141390932013411112823855/ 用jQ的人很多人都是这么开始写脚本的: $(functi ...

  10. Convolutional Patch Networks with Spatial Prior for Road Detection and Urban Scene Understanding

    Convolutional Patch Networks with Spatial Prior for Road Detection and Urban Scene Understanding 深度学 ...