一、Ioc容器

某天,小J心血来潮,决定建造一艘星舰,这艘星舰要搭载“与众不同最时尚,开火肯定棒”的电磁炮。于是他写了一个星舰类:

class ElectromagneticGun
{
public function fire() {
echo 'da da da!';
}
}
class StarShip
{
protected $gun;
public function __construct()
{
$gun = new ElectromagneticGun();
}
}
$powerfulStarShip = new StarShip();

StarShip在构造时必须要new一个ElectromagneticGun,依靠其实现fire的功能。即StarShip“依赖”于ElectromagneticGun。

这艘星舰火力强大,所向披靡。直到有一天,他碰到了速度奇快的“无敌舰队”。他决定将电磁炮更换“炮弹”速度为光速的激光炮。

class LaserGun
{
public function fire() {
echo 'biu biu biu!';
}
}

当然,最直接的办法就是在starShip的构造函数中将electromagneticGun()更改为laserGun()。但小J并不想将他的宝贝星舰大卸八块,然后塞进一个新的东西。于是他对星舰与战炮进行了控制反转(IOC)的改造,采用了依赖注入(DI)的方式。现在,他的星舰与战炮看起来像这个样子了:

interface Gun
{
public function fire();
} class ElectromagneticGun implements gun
{
public function fire() {
echo 'da da da!';
}
} class LaserGun implements Gun
{
public function fire() {
echo 'biu biu biu!';
}
} class starShip
{
protected $gun;
public function __construct(Gun $powerfulGun)
{
$gun = $powerfulGun;
}
}
$newGun = new LaserGun();
$powerfulStarShip = new starShip($newGun);

在这次改造中,原本由对象自己new的依赖,变成了在对象外部创建。获得依赖的方式转移(反转)了,即“控制反转”。

装备了激光炮的小J,在与无敌舰队的战斗中大获全胜,缴获了大量的资源。

小J想要建造更多的星舰,每艘星舰搭载一门电磁炮或一门激光炮,但作为一个程序员,他无法忍受每次想要新造一艘星舰,都要手动new一个Gun的对象,然后注入,甚至在有些时候,他会忘记为星舰new一个Gun然后Gun,导致这个星舰无法建造。他想要更加优雅的方式来建造星舰,管理武器。于是,他创建了一个简单的Ioc容器。

class Container
{
protected $binds = array(); /**在容器内注册类
* @param $abstract 类的名称
* @param $concrete 闭包,在闭包内new一个此类的对象
*/
public function bind($abstract, $concrete)
{
$this->binds[$abstract] = $concrete;
} /**解析一个对象实例
* @param $abstract 类的名称
* @param array $parameters
* @return mixed 新解析出来的对象实例
*/
public function make($abstract, $parameters = [])
{
//请注意这里,将容器本身作为第一个参数传给了闭包
array_unshift($parameters, $this); return call_user_func_array($this->binds[$abstract], $parameters);
}
} $container = new Container();
$container->bind('ElectromagneticGun',function (Container $container) {
return new ElectromagneticGun();
});
$container->bind('LaserGun',function (Container $container) {
return new LaserGun();
}); $container->bind('StarShip',function (Container $container, $gunType) {
return new StarShip($container->make($gunType));
}); $starShip1 = $container->make('StarShip',['ElectromagneticGun']);
$starShip2 = $container->make('StarShip',['LaserGun']);

在这里我们在容器内存储可生成具体实例的闭包而不是直接存储实例,其原因是显而易见的:实现某种程度的“延迟绑定”,从而减少资源消耗。这种处理技巧在后面我们看Laravel的源码是也会用到。

请注意,我们在注册容器的时候,为了能够使用容器解决StarShip的“次要依赖”(类绑定在了容器中,但生成它的对象还需要其他依赖),将容器本身作为了闭包的第一个参数。

现在,通过Ioc容器这样一个第三者,在创建StarShip对象的时候,我们完全不需要去考虑如何满足它的依赖:只要我们提前绑定好,容器就会为我们解析出我们想要的依赖。

引用几张经典的图片,我们的系统由解耦之前:



变成了解耦之后的:





这就是Ioc容器的好处。

另外,成熟的Ioc容器一般都会利用其实现语言的“反射”技术来取代上面需要我们“手写”的闭包,以及自动解决容器内的“次要依赖”。当然,对某些不支持反射的语言,就要另当别论了(没错,我说的就是C++)。。。

二、Laravel中的服务容器

Laravel框架是一个容器框架,框架应用程序的实例就是一个超大的Ioc容器,Laravel将其称为“服务容器”。

在每次请求的声明周期中,Laravel 自身的第一个动作就是创建一个服务容器的实例,它在整个请求声明周期中是唯一的。熟悉的,我们可以通过app()这个help函数以及APP这个facade来获取或访问到这个服务容器的实例。

服务容器的实现在 Illuminate\Container\Container.php 中,我们观察一下它的核心代码(精简了大部分代码):


class Container
{ //绑定数组
protected $bindings = []; //已有部分实例存储在容器内
protected $instances = []; //绑定一个名称或回调闭包到容器
public function bind($abstract, $concrete = null, $shared = false)
{ if (is_null($concrete)) {
$concrete = $abstract;
} if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
} $this->bindings[$abstract] = compact('concrete', 'shared'); } protected function getClosure($abstract, $concrete)
{
return function ($c, $parameters = []) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make'; return $c->$method($concrete, $parameters);
};
} public function make($abstract, array $parameters = [])
{ // 若请求的实例已在容器内存在,则直接返回 if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
} // 获取绑定的闭包,若未找到则返回的$abstract的上下文绑定或更改了形式(加斜杠前缀?)的$abstract
$concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete, $parameters);
} else {
$object = $this->make($concrete, $parameters);
} return $object;
} public function build($concrete, array $parameters = [])
{
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
} $reflector = new ReflectionClass($concrete); //获取构造函数
$constructor = $reflector->getConstructor(); //没有构造函数,就意味着没有依赖,可以立即构造
if (is_null($constructor)) {
array_pop($this->buildStack); return new $concrete;
} //获取构造函数的所有参数(依赖)
$dependencies = $constructor->getParameters(); //解决依赖
$instances = $this->getDependencies(
$dependencies, $parameters
); return $reflector->newInstanceArgs($instances);
} //通过反射机制解决所有的依赖
protected function getDependencies(array $parameters, array $primitives = [])
{
$dependencies = []; foreach ($parameters as $parameter) {
$dependency = $parameter->getClass(); if (array_key_exists($parameter->name, $primitives)) {
$dependencies[] = $primitives[$parameter->name];
} elseif (is_null($dependency)) {
$dependencies[] = $this->resolveNonClass($parameter); //解决一个不是类的依赖:若允许默认值则返回默认值,否则抛出异常
} else {
$dependencies[] = $this->resolveClass($parameter); //解决一个类的依赖:从Container内解析此类。
}
} return (array) $dependencies;
} }

bind() 方法中会处理两种情况:

  1. concrete 为闭包:直接添加至bindings数组
  2. concrete 为类名:生成一个闭包将其包裹起来( getClosure方法)

make()方法会查找请求的实例,若已在容器内构造好了,则直接返回构造好的实例。

否则利用isBuildable方法判断是否可构造(判断标准为是否已注册或为明确的类名),若可构造则利用build方法进行构造,否则更改abstract继续查找。

build() 方法会检测concrete是否为闭包。若为闭包,直接构造即可。若为类名,则利用反射机制进行构造,还要解决其所有依赖。

以上的服务容器源码是大幅精简之后的,完整的源码还有许多诸如别名、绑定实体、获取容器属性等等的处理与方法。但其实我们可以发现其和我们在上面实现的“幼儿园”版Ioc容器的思想是一样的:利用一个array存储可生成对象的闭包,从而能够作为一个第三者,为各组件提供自动化的依赖注入,实现了组件之间的解耦。

三、服务提供者

只要能够拿到服务容器的实例,我们就可以进行类(服务)的绑定。但若我们不进行一个统一的绑定/初始化操作,可能会出现需要某一对象时,忘记绑定其依赖的情况。那岂不是失去了服务容器的原本意义?所以Laravel提供了服务提供者,能够使我们在业务代码开始之前,应用程序初始化的时候进行绑定。

laravel加载自定义服务提供者的时候,是从config/app.php这个配置文件里面的providers中找到所有的服务提供者的。

所以你如果自己写了一个服务提供者,那么只要配置到这里面,laravel就会自动帮你注册它了。

其主要提供了两个方法:

register与boot

Laravel通过配置文件找到服务提供者的类后,构造一个对象,然后会调用这个对象的register方法。所以我们可以在服务提供者的register方法中将类(服务)绑定至服务容器中。注意一定不要在register方法中使用一些未注册的类(服务)。

在所有的服务提供者都注册完成之后,会执行boot方法,所以这时候就可以尽情地使用服务容器来解析出实例来了。

参考资料:

http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html

https://www.martinfowler.com/articles/injection.html

http://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html

https://laravel-china.org/articles/789/laravel-learning-notes-the-magic-of-the-service-container

https://www.cnblogs.com/lyzg/p/6181055.html

http://www.ituring.com.cn/article/505006

Ioc容器与laravel服务容器初探的更多相关文章

  1. Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)

    容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存放的不是文本.数值,而是对象.对象的描述(类.接口)或 ...

  2. laravel服务容器

    laravel框架底层解析 本文参考陈昊<Laravel框架关键技术解析>,搭建一个属于自己的简化版服务容器.其中涉及到反射.自动加载,还是需要去了解一下. laravel服务容器 建立项 ...

  3. laravel服务容器 转

    laravel框架底层解析 本文参考陈昊<Laravel框架关键技术解析>,搭建一个属于自己的简化版服务容器.其中涉及到反射.自动加载,还是需要去了解一下. laravel服务容器 建立项 ...

  4. Laravel 服务容器、服务提供器、契约实例讲解

        前言 刚开始看laravel服务容器.契约.服务提供器的确生涩难懂,不单单是概念繁多,而且实际的demo很难找(找是找到了,但难用啊),最后就隔一段时间看一遍,大概个十来遍,还真给看出个门道, ...

  5. Laravel服务容器的绑定与解析

    本篇文章给大家带来的内容是关于Laravel服务容器的绑定与解析,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言   老实说,第一次老大让我看laravel框架手册的那天早上,我 ...

  6. 关于使用 Laravel 服务容器的优势介绍

    如果说laravel框架的核心是什么,那么无疑是服务容器.理解服务容器的概念,对于我们使用laravel太重要了,应该说是否理解服务容器的概念是区分是否入门laravel的重要条件.因为整个框架正是在 ...

  7. laravel服务容器-----深入理解控制反转(IoC)和依赖注入(DI)

    首先大家想一想什么是容器,字面意思就是盛放东西的东西,常见的变量,对象属性都是容器,一个容器能够装什么东西,完全在于你对这个容器的定义.有的容器不仅仅只是存文本,变量,而是对象,属性,那么我们通过这种 ...

  8. Laravel 服务容器,IoC,DI

    DI DI 就是常说的依赖注入,那么究竟什么是依赖注入呢? 打个比方,电脑(非笔记本哈)需要键盘和鼠标我们才能进行操作,这个‘需要’换句话说就是‘依赖’键盘和鼠标. 那么,相应的,一个类需要另一个类才 ...

  9. laravel 服务容器实例——深入理解IoC模式

    刚刚接触laravel,对于laravel的服务容器不是很理解.看了<Laravel框架关键技术解析>和网上的一些资料后对于服务容器有了一些自己的理解,在这里分享给大家 1.依赖 IoC模 ...

随机推荐

  1. image的路径写法格式

    if (MapGrid.Visibility == Visibility.Visible) {      this.MapGrid.Visibility = Visibility.Collapsed; ...

  2. hdu 1281 匈牙利算法

    棋盘游戏 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  3. 通过ADB调试安卓程序

    ADB,即 Android Debug Bridge,它是Android开发/测试人员不可替代的强大工具. 1.下载ADB后,将以下四个文件放到某个文件夹下即可.因为打开Cmd默认路径是 C:\Use ...

  4. 【js】null 和 undefined的区别?

    1.首先看一个判断题:null和undefined 是否相等     console.log(null==undefined)//true     console.log(null===undefin ...

  5. win10下 安装迅雷精简版

    下载链接:https://files-cdn.cnblogs.com/files/del88/ThunderMini_1.5.3.288.zip 他妈的 今天安装迅雷精简版 在win10上 竟然报错, ...

  6. 轮播图--使用原生js的轮播图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 封装AJAX库(参考JQ)

    //jQ方法 $.ajax([URL],[OPTIONS]) $.ajax({ url:'', data:null, datatype:'json', method:'GET', async:true ...

  8. 转自:java 文件格式二进制头文件校验

    转自:https://blog.csdn.net/useprogram/article/details/90637401public class FileTypeUtil { private fina ...

  9. Python的Struct模块

    python strtuct模块主要在Python中的值于C语言结构之间的转换.可用于处理存储在文件或网络连接(或其它来源)中的二进制数据. #!/usr/bin/env python # -*- c ...

  10. uni-app中nvue (weex) 注意事项

    前言 uni-app 是 DCloud 出品的新一代跨端框架,可以说是目前跨端数最多的框架之一了,目前支持发布到:App(Android/iOS).H5.小程序(微信小程序/支付宝小程序/百度小程序/ ...