看laravel源码学习依赖注入

Posted varxinyuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看laravel源码学习依赖注入相关的知识,希望对你有一定的参考价值。

前言

初心

最近在看设计模式中的依赖注入,希望借助设计模式的神奇魔力,能达到一个目的,然后在此学习的过程中,能收获一个bonus。
这个目的就是能使得自己设计的系统更简单更容易理解,或者是使得系统设计的结构和代码更简单,而bonus是企图在设计模式上实现概念上的并发。
这篇文章是希望把自己这段时间的学习成果作一个记录和总结,然而并不要太期待,因为目前得出的结论并没有达到我的目的,bonus暂时希望也比较渺茫。

在知识网络结构认知上的准备

我们需要提前了解一下依赖倒置原则、控制反转和依赖注入的关系。
控制反转是一种设计思想,它遵循了依赖倒置原则,而实现控制反转一般主要的方式或者手段是依赖注入。
依赖倒置原则本文就不作解释了,我们了解一下即可。

唠一唠依赖注入和IoC

依赖注入是什么东西呢

这里有几个先行的概念需要描述一下,首先我们要有一个主体,这个主体注入依赖主体通过使用这些依赖去做一些行为或者实施一些操作。
主体指的是一些系统设计上的一些实体,比如有一个违规记录类,用于实现创建、修改、审核的业务逻辑,那么这个违规记录类就是一个主体,再比如有一个这样的业务逻辑:违规记录内包含多个违规指标。这时候这个违规指标类,也是一个主体
依赖指的是主体需要依赖另外某个主体实现某个功能,比如刚刚的例子里的违规记录类需要依赖违规指标类去实现创建违规记录的逻辑,创建的逻辑里需要记录该条违规记录有哪些违规指标,以及需要作违规指标的业务校验,这里违规记录类这个主体需要依赖违规指标类这个主体实现一些动作,那么违规指标类这个主体就是违规记录类这个主体依赖
至于注入,指的是主体如何获取依赖,常见的方式有通过构造函数参数传入和类方法参数传入,简单而言是这样:

依赖 = new 主体B();
对象 = new 主体A(依赖);
对象.实现功能();
IoC容器
啥是IoC

什么是Inversion Of Control(控制反转)呢,这里我们要先理解一下控制正转,对其他主体或者流程的控制是完全限制在主体以内的就是控制正转,其实也就是我们平时写代码的姿势,举例来说就是:

class A
{
  public function 实现功能()
  {
    对象B = new B();
    对象B.检查一下商品数量();
    对象C = new C();
    对象C.检查一下优惠();
    return "牛逼!";
  }
}

对象A = new A();
对象A.实现功能();

控制反转是指的对其他主体的控制不在主体以内,主体并不需要亲自创建对象,而是通过注入的方式获得对象,是的,上面依赖注入的例子就是控制反转,一般会使用依赖注入来实现控制反转,举例来说就是:

class A
{
  B 对象B;
  C 对象C;
  public __construct(B 对象B, C 对象C)
  {
    this.对象B = 对象B;
    this.对象C = 对象C;
  }
  public function 实现功能()
  {
    this.对象B.检查一下商品数量();
    this.对象C.检查一下优惠();
    return "牛逼!";
  }
}

// 同时也是依赖注入的例子
对象B = new B();
对象C = new C();
对象A = new A(对象B, 对象C);
对象A.实现功能();
为什么要有IoC容器,要实现什么效果

好的!介绍完IoC的概念,那什么是IoC容器呢,其实就是将创建对象的工作收到一个”容器“内,这样代码就会变得更加简单和简洁。
在上面的例子的基础上,要实现的效果简单来说就是:

// 类A代码没有变动

容器 = new 容器();
容器.绑定(B);
容器.绑定(C);
容器.绑定(A);

对象A = 容器.制造(A);
对象A.实现功能();

laravel IoC 容器详解

使用 IoC 容器注入依赖,我们可以更加方便的管理和使用依赖,这里有必要研究一下IoC容器的具体实现,我们可以找一找业界有名的实现:laravel的 IoC 容器,探一探其中究竟。
IoC容器的基本动作有两个,绑定和生产对象,我们可以基于这两个容器的基本操作,来进入IoC容器的内部。

IoC容器的基本操作

基本使用方法

laravel IoC容器的使用方法基本和上述容器的使用例子一致,一个是对主体进行绑定,这里的主体指的是某个类或者是某个对象;另外一个是创建对应的对象,才能使用该对象完成各种业务逻辑或者功能。

绑定

laravel 的容器会维护多个绑定关系,用于在生产对象的时候解析到对应的类,才能创建出这个类的对象,绑定关系大概有以下几种,有兴趣的读者可以查阅container类的属性,这里对绑定关系及其调用关系作一个简单的介绍,以供参考:

  • $resolved: 记录某个绑定关系是否已被解析,也就是是否该对象被创建过;
  • $bindings: 记录 Interface A 的实现为 类 a,也就是数组 [A => a];
  • $methodBindings: 记录某类与某方法绑定起来,具体原理待深究;
  • $instances:记录某个对象,创建对象时直接将此对象返回;
  • $aliases:记录某个类及其别名,比如是["类 A" => "cache(类 A 的别名)"]
  • $abstractAliases: 记录某别名下有哪些类,举例来说是 ["cache" => ["redis 类", "memcache 类"]]
  • $extenders:将某类与某些闭包函数绑定起来,可以通过 getExtenders 方法获取这些闭包函数,具体原理待深究;
  • $tags: 将某些类打上tag,数据结构是["cache_tag" => ["redis 类", "memcache 类"]],可以批量操作,具体用法待深究;
  • $buildStack:记录所有已被创建的对象;
  • $with: 记录所有已被创建对象的参数;
  • $contextual:增加一个上下文绑定,具体原理待深究;
  • $reboundCallbacks:待深究
  • $globalResolvingCallbacks:待深究
  • $globalAfterResolvingCallbacks:待深究
  • $resolvingCallbacks;待深究
  • $afterResolvingCallbacks:待深究

部分调用关系:

resolved[]:
1. Container.resolved
2. Container.resolve
3. Container.flush
4. Container.offsetUnset

bindings[]:
1. Container.bind
2. Container.isShare
3. Container.getConcrete
4. Container.flush
5. Container.offsetUnset
6. Container.Bound
7. Container.getBindings

instances[]:
1. Application.make //只用于判断,然后延迟注册provider
2. Container.bound
3. Container.resolved
4. Container.isShared
5. Container.extend
6. Container.instance
7. Container.resolve
8. Container.dropStaleInstances
9. Container.forgetInstance
10. Container.forgetInstances
11. Container.flush
12. Container.offsetUnset

aliases[]:
1. Container.isAlias
2. Container.instance
3. Container.removeAbstractAlias
4. Container.alias
5. Container.getAlias
6. Container.dropStaleInstances
7. Container.flush

abstractAliases[]: 记录某类有哪些alias
methodBindings[]: 方法绑定
extenders[]: 扩展关系
tags[]: 给abstracts打tag

......

下述详细介绍的是bindings、intances以及aliases。

  • bindings
    这个可以称为类关系的绑定,也就是容器解析的时候所得到创建对象的方式,可以是类名或者是创建对象的闭包函数,根据该类名或函数可以创建出对应的对象。bindings可以分类以下几类:

    • 绑定自身,用代码展示更明了:
      $this->app->bind(‘AppServicesRedisEventPusher‘, null);
      
    • 绑定闭包
      $this->app->bind(‘HelpSpotAPI‘, function ($app) {
        return new HelpSpotAPI();
      });//闭包直接提供实现方式
      
    • 绑定接口
      $this->app->bind(
        ‘AppContractsEventPusher‘,  // Interface
        ‘AppServicesRedisEventPusher‘   // 实现功能的具体类
      );
      
  • instances
    intances绑定是指的绑定已创建的对象,并不需要容器帮助创建对象,简单来说是:

    $api = new HelpSpotAPI(new HttpClient);
    $this->app->instance(‘HelpSpotApi‘, $api);
    
  • aliases
    别名的绑定,简言之就是维护一个KV对,K是别名,V是对象类名(或者反过来(aliases)),这个直接看源码更一目了然:

    foreach ([
          ‘db‘                   => [IlluminateDatabaseDatabaseManager::class],
          ‘db.connection‘        => [IlluminateDatabaseConnection::class, IlluminateDatabaseConnectionInterface::class],
          ‘events‘               => [IlluminateEventsDispatcher::class, IlluminateContractsEventsDispatcher::class],
          ......
      ] as $key => $aliases) {
          foreach ($aliases as $alias) {
              $this->alias($key, $alias);
          }
    }
    

    那$this->alias的实现呢?alias方法的实现非常简单,别名的解析就不在下述IoC执行过程里描述了,主要是维护aliases和abstractAliases两个别名关系,分别是两个php数组,一贴源码诸位便知>.<:

    public function alias($abstract, $alias)
    {
      ...
      $this->aliases[$alias] = $abstract;
      $this->abstractAliases[$abstract][] = $alias;
    }
    
解析及生产对象

就是使用make去创建对象。

public function make($abstract, array $parameters = [])
{
    // resolve 方法解析对象
    return $this->resolve($abstract, $parameters);
}

IoC容器的执行流程

上面讲述了laravel容器如果使用,也就是如何使用容器对应方法绑定类/对象,以及创建我们需要的对象。
下面我们看一看bind动作和make动作的具体实现过程。

bind动作过程
public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact(‘concrete‘, ‘shared‘);

        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
make动作过程(resolve)
instance动作过程

以上是关于看laravel源码学习依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

Laravel源码学习之Container

Laravel 学习笔记:深入理解控制反转(IoC)和依赖注入(DI)

.NET 通过源码深究依赖注入原理

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

ThinkPHP6源码:从Http类的实例化看依赖注入是如何实现的