框架启动与服务容器源码

了解了服务容器的原理,要处理的问题,以及 Laravel 中如何使用服务容器以及服务提供者之后,我们就进入到了源码的学习中。其实服务容器的源码还是比较好理解的,毕竟我们已经自己实现过一个简单的服务容器了。在这里,我们也顺便看一下 Laravel 框架启动时的容器加载情况。

框架启动

通过之前的学习,我们已经了解到 Laravel 是单一入口文件的框架。所以我们直接去 public/index.php 查看这个入口文件。

// public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';$kernel = $app->make(Kernel::class);$response = tap($kernel->handle($request = Request::capture()
))->send();$kernel->terminate($request, $response);

天啊,这也太明显了吧,上来就加载了一个 bootstrap/app.php 这个文件,然后就开始使用 $app->make() 来调用容器的实现方法了。那么我们很清楚地就可以发现,这个 bootstrap/app.php 就是一个服务容器。话不多说,马上进入到 bootstrap/app.php 文件中。

$app = new Illuminate\Foundation\Application($_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);$app->singleton(Illuminate\Contracts\Http\Kernel::class,App\Http\Kernel::class
);$app->singleton(Illuminate\Contracts\Console\Kernel::class,App\Console\Kernel::class
);$app->singleton(Illuminate\Contracts\Debug\ExceptionHandler::class,App\Exceptions\Handler::class
);return $app;

我们首先实例化了一个 Illuminate\Foundation\Application 对象,然后再实例化了几个单例服务,分别是 Http 的核心 Kernel 和命令行 Console 的核心 Kernel 对象,另外还有一个异常控制对象。到这里,你也一定会想到了,这个 Illuminate\Foundation\Application 就是我们整个 Laravel 框架的核心,也就是服务容器实现的核心。

Container 服务容器

打开 laravel/framework/src/Illuminate/Foundation/Application.php 文件,我们可以看到这个类继承的是一个叫做 Container 的类,这个单词就是容器的意思。从这里我们就可以看出,Laravel 是以 Application 也就是应用的意思来代替容器,但其实这个应用就是一个容器。由此可见,本身整个运行起来的 Laravel 就是一个超大的 Application 应用。

bind

在 Application 中,我们可以看到熟悉的 make() 和 boot() 方法,而 bind()、instance()、singleton() 方法则都在它的父类 Container 中实现的,我们先来看看 bind() 方法。

public function bind($abstract, $concrete = null, $shared = false)
{$this->dropStaleInstances($abstract);if (is_null($concrete)) {$concrete = $abstract;}if (! $concrete instanceof Closure) {if (! is_string($concrete)) {throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');}$concrete = $this->getClosure($abstract, $concrete);}$this->bindings[$abstract] = compact('concrete', 'shared');if ($this->resolved($abstract)) {$this->rebound($abstract);}
}

首先 dropStaleInstances() 是如果已经有同名的容器实现,也就是 instaces 数组中有的话,清理掉它,然后看实现参数 concrete 是否为空,如果为空的话把容器名称赋值给实现。接下来,判断实现是否是匿名函数形式的,如果不是的话,转换成一个匿名函数形式的实现方法。然后通过 compact() 函数将参数转换成数据并保存在 bindings 数组中。

想必这两个 instances 和 bindings 是干什么的不用我再多解释了吧。最后的 resolved() 方法是判断这个服务是否在默认的别名应用中,是否已经有 resolved 解决方案实例,如果有的话,调用 rebound() 对象 make() 它出来。

很明显,框架的代码比我们实现的服务容器代码可复杂多了,但是大体思想是一致的。至于后面的一些比较诡异的 resolved() 和 rebound() 是干嘛用的,我们后面再说。

singleton() 方法的实现就是调用的 bind() 方法,只不过最后一个 $shared 参数默认给了一个 ture 。从名字可以看出,这个 shared 是共享的意思,而 singleton 是单例的意思,暂时我们推测,在 make() 的时候,我们会根据这个变量来确定要实现加载的这个对象是不是使用单例模式。答案我们在看 make() 方法的时候再研究。

instance

singleton() 方法直接调用的就是 bind() 方法,只是最后一个参数默认给了一个 true ,所以我们也就不多说了,主要来看一下另外一个 instance() 方法。其实从上面代码就可以看出,bind() 方法的第二个参数只能是 Closure 或者 string 以及 null 类型的。如果我们想直接绑定一个实例,就需要使用 instance() 方法。

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);}return $instance;
}

在之前我们自己实现的那个容器类中,在 bind() 方法中直接进行了判断,如果是实例则直接放到 instances 数组中,而在 Laravel 中,则是分开了,必须在 instance() 方法中才会将实例保存到 instances 数组。

make

最后我们再来看一下 make() 方法,也就是从服务容器中获得我们的需要的对象。

public function make($abstract, array $parameters = [])
{return $this->resolve($abstract, $parameters);
}protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{$abstract = $this->getAlias($abstract);if ($raiseEvents) {$this->fireBeforeResolvingCallbacks($abstract, $parameters);}$concrete = $this->getContextualConcrete($abstract);$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {return $this->instances[$abstract];}$this->with[] = $parameters;if (is_null($concrete)) {$concrete = $this->getConcrete($abstract);}if ($this->isBuildable($concrete, $abstract)) {$object = $this->build($concrete);} else {$object = $this->make($concrete);}foreach ($this->getExtenders($abstract) as $extender) {$object = $extender($object, $this);}if ($this->isShared($abstract) && ! $needsContextualBuild) {$this->instances[$abstract] = $object;}if ($raiseEvents) {$this->fireResolvingCallbacks($abstract, $object);}$this->resolved[$abstract] = true;array_pop($this->with);return $object;
}

make() 方法实际上调用的是 resolve() 这个方法,在这个方法内部,我们可以看到最后直接返回的就是一个 $object 变量,很明显,它将会是一个对象。这个 $object 是通过前面的一系列判断并调用相应的方法来获得的,通过 getAlias() 我们会获得需要实例化的对象是否有别名设置,这个设置主要是框架内部的很多对象都会进行一个别名配置,通常是框架比较核心的一些组件,然后 getContextualConcrete() 我们会获得当前容器中绑定的对象信息,接下来在 isBuildable() 中,判断容器名是否和我们传递过来的名称相同,以及容器内容是否是一个回调函数。如果两者有其一符合条件就进入 build() 方法,如果都不符合使用查找到的容器名两次调用 make() 方法。从这里我们会发现,服务实例化的核心转移到了 build() 方法中。

public function build($concrete)
{if ($concrete instanceof Closure) {return $concrete($this, $this->getLastParameterOverride());}try {$reflector = new ReflectionClass($concrete);} catch (ReflectionException $e) {throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);}if (! $reflector->isInstantiable()) {return $this->notInstantiable($concrete);}$this->buildStack[] = $concrete;$constructor = $reflector->getConstructor();if (is_null($constructor)) {array_pop($this->buildStack);return new $concrete;}$dependencies = $constructor->getParameters();try {$instances = $this->resolveDependencies($dependencies);} catch (BindingResolutionException $e) {array_pop($this->buildStack);throw $e;}array_pop($this->buildStack);return $reflector->newInstanceArgs($instances);
}

在 build() 方法中,先判断绑定的容器内容是不是一个回调函数,如果是的话,直接调用这个回调函数并且返回了。如果不是回调函数的话,下面的内容相信大家也不会陌生了,通过 反射 的方式来创建对象。高大上不,如果你在 bind() 方法中,使用的是一个 \App\ContainerTest\iPhone12::class ,这样的类字符串,那么它就会通过反射来生成这个对应的对象。

resolveDependencies() 用来解决类实例化时构造函数的依赖问题,需要的参数也是通过上面反射时 getParameters() 方法获取的。

ServiceProvider 服务提供者

通过上面的几个方法学习,我们了解到了整个 Laravel 容器中最重要的几个方法,也就是绑定实现以及获得具体的实例对象,是不是和我们自己实现的那个服务容器非常像。当然,就像之前我们说过的,在框架中的实现会比我们自己的实现要复杂很多。接下来我们看看服务提供者是怎么加载的。

回到 public/index.php 中,我们可以看到一段代码。

$response = tap($kernel->handle($request = Request::capture()
))->send();

这里调用了 kernel 的 handle() 方法,进入 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 的 handle() 方法之后继续查看 sendRequestThroughRouter() 方法,在这个方法中调用了一个 bootstrap() 方法。

// vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
public function bootstrap()
{if (! $this->app->hasBeenBootstrapped()) {$this->app->bootstrapWith($this->bootstrappers());}
}// vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function bootstrapWith(array $bootstrappers)
{$this->hasBeenBootstrapped = true;foreach ($bootstrappers as $bootstrapper) {$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);$this->make($bootstrapper)->bootstrap($this);$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);}
}

$this->bootstrappers() 返回的就是在 Kernel 中的那个 bootstrappers 属性,然后通过 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中的 bootstrapWith() 方法来加载这些预定义的服务提供者。

不对呀,这里都是预定义的服务提供者,我们自定义的那些服务提供者是在哪里加载的呢?

protected $bootstrappers = [\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,\Illuminate\Foundation\Bootstrap\HandleExceptions::class,\Illuminate\Foundation\Bootstrap\RegisterFacades::class,\Illuminate\Foundation\Bootstrap\RegisterProviders::class,\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

注意看 RegisterProviders ,bootstrapWith() 方法会直接调用这些预定义服务提供者的 bootstrap() 方法,而 RegisterProviders 中的 bootstrap() 方法只有一行代码。

public function bootstrap(Application $app)
{$app->registerConfiguredProviders();
}

它又回来继续调用 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中的 registerConfiguredProviders() 方法。

public function registerConfiguredProviders()
{$providers = Collection::make($this->config['app.providers'])->partition(function ($provider) {return strpos($provider, 'Illuminate\\') === 0;});$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))->load($providers->collapse()->toArray());
}

其实到这里就已经很明显了,我们看到了 $this->config['app.providers'] 这个变量,它就是获得的 config/app.php 中的 providers 里面的内容,然后通过后面的代码将这些服务提供者注册到服务容器中。

当所有定义好的服务提供者注册完成后,会继续进行 $bootstrappers 中 BootProviders 服务提供者的注册,它会调用每个服务提供者的 boot() 方法完成各个服务的启动加载。这一下,你就知道为什么 boot() 方法可以调用到所有的服务了吧。

框架核心

通过来回查看 Kernel 和 Application ,相信你已经明白整个框架的核心就是在这两个类之间来回倒腾。默认的服务实例以及服务提供者都在 Application 的构造函数中进行了预加载,比如说路由、门面等等。而我们自定义的那些服务提供者则是通过 RegisterProviders 并进行配置读取后也完成了加载。

除些之外 Application 的 registerCoreContainerAliases() 中做好了许多别名对象的服务配置,当你搞不清楚为什么 $this->make('app') 可以使用的时候,就可以到这里来看一看。这些别名实例的定义最大的用途其实是在 门面 中使用,这个我们后面在讲到门面的时候还会再说。

总结

其实关于服务容器还有很多值得我们深入学习和挖掘的内容,但限于篇幅和本人的水平有限,这里只是梳理了一个大概的流程。大家可以继续顺着这两个核心的类,也就是 Kernel 和 Application 继续研究和探索,相信你的收获一定会更多。

【Laravel系列6.3】框架启动与服务容器源码相关推荐

  1. php的lumen框架,Lumen框架“服务容器”源码解析

    1.服务容器 "服务容器"是Lumen框架整个系统功能调度配置的核心,它提供了整个框架运行过程中的一系列服务."服务容器"就是提供服务(服务可以理解为系统运行中 ...

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

    服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...

  3. Spring IOC 容器源码分析系列文章导读

    1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...

  4. Spring IOC 容器源码分析系列文章导读 1

    1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...

  5. Eureka服务注册源码分析

    本文来说下Eureka服务注册源码 文章目录 Eureka-Client注册服务 啥时候会注册 定时器注册 自动注册 DiscoveryClient.register() Eureka-Server接 ...

  6. 【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码

    目录 ★☆★ 写在前面 ★☆★ ★☆★ 本系列文章 ★☆★ ★☆★ 开源网址 ★☆★ 一.给 Swing 加上 Spring 0.前期努力 I. SpringBoot II. SpringMVC 1. ...

  7. java sofa rpc_sofa-rpc服务端源码的详细分析(附流程图)

    本篇文章给大家带来的内容是关于sofa-rpc服务端源码的详细分析(附流程图),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. sofa-rpc是阿里开源的一款高性能的rpc框架,这篇 ...

  8. 畅玩mt3单机游戏服务器维护,【梦幻西游】MT3仿端手工游戏服务端源码[教程+授权物品后台]...

    [梦幻西游]MT3仿端手工游戏服务端源码[教程+授权物品后台] 架设教程 系统:CentOS 6.8  64位 1.关闭防火墙 chkconfig iptables off service iptab ...

  9. java计算机毕业设计农产品供销服务系统源码+系统+mysql数据库+lw文档

    java计算机毕业设计农产品供销服务系统源码+系统+mysql数据库+lw文档 java计算机毕业设计农产品供销服务系统源码+系统+mysql数据库+lw文档 本源码技术栈: 项目架构:B/S架构 开 ...

  10. java毕业设计融呗智慧金融微资讯移动平台服务端源码+lw文档+mybatis+系统+mysql数据库+调试

    java毕业设计融呗智慧金融微资讯移动平台服务端源码+lw文档+mybatis+系统+mysql数据库+调试 java毕业设计融呗智慧金融微资讯移动平台服务端源码+lw文档+mybatis+系统+my ...

最新文章

  1. Java项目:在线蛋糕商城系统(java+jsp+jdbc+mysql)
  2. 第二章 序列比对——Needleman-Wunsch全局比对
  3. 电脑编程python老是出现错误_python常见的编程错误
  4. AI开发者大会之语音语义技术实践与应用:2020年7月3日《NLP在教育行业的应用》、《AI防疫-语音语义技术在政务联络场景中的应用》、《智能客服机器人在售前导购场景中的应用实践》
  5. C#中的DBNull、Null、和String.Empty解释【转】
  6. kernel32.dll出错解决方案
  7. c++检测ip是否匹配子网掩码_网络工程师从入门到精通通俗易懂系列 | ARP和IP这篇文章讲的相当详细了,这么基础的知识往往也是最容易遗忘的!...
  8. [总]Android高级进阶之路
  9. autoComplete实现的输入下拉联想功能
  10. 编辑器Sublime Text安装配置
  11. 2022全新版千月影视源码原生播放器 投屏 选集 下载应有尽有(全开源)
  12. Linux内核研究系列之可执行文件格式(转)
  13. 登录服务器手机验证码不正确,解决织梦后台登录一直提示验证码不正确的方法...
  14. 统一社会信用代码校验-JavaScript
  15. python教你用骰子拼图
  16. unittest跳过用例方式
  17. 感觉编程还是需要逐渐熟练的技术工种吧,分几个维度来描述。
  18. VR技术与生态:大咖跟你聊VR未来
  19. leaflet绘制具有虚线框的多边形(125)
  20. 象棋软件新霸主诞生,旋风绝杀名手,再次夺冠,一统江湖了

热门文章

  1. Apache Tomcat 文件包含漏洞(CNVD-2020-10487,对应 CVE-2020-1938)
  2. 计算机408考研经验分享
  3. guava-retry介绍
  4. 扒一扒 ScheduledThreadPoolExecutor
  5. 《领导力与沟通艺术》
  6. C语言lcd实现奥运五环编程,如何用C语言程序来设计奥运五环图案
  7. vb.net写的一个简单计算器(未完善)
  8. 使用python的模拟退火算法估计heston期权定价模型的五个参数(新)
  9. 基于Java+Springboot+Vue校园志愿者管理系统设计与实现
  10. 复制csdn或者博客园文章时,图片无法直接粘贴过来解决办法