看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(‘App\Services\RedisEventPusher‘, null);
      
    • 绑定闭包
      $this->app->bind(‘HelpSpot\API‘, function ($app) {
        return new HelpSpot\API();
      });//闭包直接提供实现方式
      
    • 绑定接口
      $this->app->bind(
        ‘App\Contracts\EventPusher‘,  // Interface
        ‘App\Services\RedisEventPusher‘   // 实现功能的具体类
      );
      
  • instances
    intances绑定是指的绑定已创建的对象,并不需要容器帮助创建对象,简单来说是:
    $api = new HelpSpot\API(new HttpClient);
    $this->app->instance(‘HelpSpot\Api‘, $api);
    
  • aliases
    别名的绑定,简言之就是维护一个KV对,K是别名,V是对象类名(或者反过来(aliases)),这个直接看源码更一目了然:
    foreach ([
          ‘db‘                   => [\Illuminate\Database\DatabaseManager::class],
          ‘db.connection‘        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
          ‘events‘               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::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动作过程

原文地址:https://www.cnblogs.com/varXinYuan/p/11546415.html

时间: 2024-11-05 19:04:57

看laravel源码学习依赖注入的相关文章

Abp 源码研读 - 依赖注入

Abp 框架对于依赖注入的实现主要是依赖 Castle.Core ,实际上这一篇更应该归类于 Castle.Core 的应用, 若对 Castle.Core 知识不了解的, 可以先去学习下:Castle.Core .下面来分析下比较重要的接口/类: 核心接口 IIocManager 定义了对 Ioc 对象, 服务注册,服务解析,服务注册与否的判断,以及对解析对象的释放. IocManager 实现了 IIocManager 接口, 这里需要特别注意的是:对于长时间运行的程序,比如网站,Windo

Laravel源码学习之Container

Ioc模式 Ioc(Inversion of Control)模式又称依赖注入(Dependency Injection)模式.控制反转就是将组件之间的依赖关系从程序的内部转移到程序外部,而依赖注入是指组件的依赖关系通过外部参数或其他形式注入,两种说法从本质上是一样的. 下面是一个简单的依赖注入实例: <?php interface Visit { public function go(); } class Leg implements Visit { public function go()

Spring源码学习笔记(6)

Spring源码学习笔记(六) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门. 上一篇中我们梳理到 Spring 加载 XML 配置文件, 完成 XML 的解析工作,接下来我们将进入 Spring 加载 bean 的逻辑. 我们使用 Spring 获取 XML

spring源码学习(1)——spring整体架构和设计理念

Spring是在Rod Johnson的<Expert One-On-One J2EE Development and Design >的基础上衍生而来的.主要目的是通过使用基本的javabean来完成以前只能用EJB完成的事情降低企业应用的复杂性.这一系列源码学习是基于Spring-4.3.11版本的. 一.Spring的整体架构 如图所示,spring可以被总结为一下几个部分: (1)Core Container 为Spring的核心容器,包含Beans,Core,Context和SpEL

Spring源码学习笔记(7)

Spring源码学习笔记(七) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门 写前说句话, 开篇不尴尬 ---- 接下的这一篇当中, 我们将来回顾 Spring 中 AOP 功能的实现流程.  早上精力充沛, 开始新一天的学习 \(^o^)/~ 接触过 Spri

Laravel源码分析--Laravel生命周期详解

一.XDEBUG调试 这里我们需要用到php的 xdebug 拓展,所以需要小伙伴们自己去装一下,因为我这里用的是docker,所以就简单介绍下在docker中使用xdebug的注意点. 1.在phpstorm中的 Perferences >> Languages & Framework >> PHP >> debug >> DBGp Proxy 中的Host填写的是宿主机的IP地址.可以在命令行中使用ifconfig / ipconfig查看你的本

Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设计精巧,代码优美,值得每一名开发人员学习阅读. 在我最开始学习javaEE时,第一次接触Spring是从一个S(Struts)S(Spring)H(Herbinate)的框架开始.由java原生开发到框架开发转换过程中,那时我的印象里Struts负责控制层,herbinate负责数据层,而Sprin

lodash源码学习(10)

_.delay(func, wait, [args]) 延迟wait毫秒之后调用该函数,添加的参数为函数调用时的参数 //delay.js var baseDelay = require('./_baseDelay'),//baseDelay方法 baseRest = require('./_baseRest'),//创建使用rest参数方法 toNumber = require('./toNumber');//转化为数字 /** * * @param {Function} func 需要延迟执

lodash源码学习(2)

继续学习lodash,依然是数组的方法 “Array” Methods _.indexOf(array, value, [fromIndex=0]) 获取value在数组 array所在的索引值 使用 SameValueZero方式比较(第一个全等===的元素). 如果 fromIndex 值是负数, 则从array末尾起算 该方法依赖于strictIndexOf和baseIndexOf方法,先看它们的源码 //_strictIndexOf.js /** * _.indexOf的专业版本,对元素