聊一聊Laravel的核心----服务提供者


Laravel中的又一个核心概念就是“服务提供者”,在 简单理解Laravel的核心概念----服务容器、服务提供者、契约中我们其实已经大致了解了服务提供者是个什么东西,本质上就就是把服务注册、绑定到容器上的地方,便于之后请求到来时使用这些服务。

基本概念

我们知道 「服务提供者」是配置应用的中心,它的主要工作是使用「服务容器」实现服务容器绑定、事件监听器、中间件,甚至是路由的注册。其基本流程就是当接收到Http/Console请求时去执行「服务提供者的 register(注册)」,将各个服务绑定到容器内,到了实际处理请求的过程根据请求使用情况进行按需加载所需服务。
除了核心容器外,几乎所有的服务提供者都配置在config/app.php中的providers数组中。为什么说是「几乎」呢?因为一些核心服务提供者并没有定义在config/app.php的providers数组中,而是直接由服务容器在实例化阶段就完成了注册服务。

namespace Illuminate\Foundation;

...

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

	...

    /**
     * Register all of the base service providers.
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }
}

我们可以看到,在服务容器被注册的阶段,已经有EventServiceProvider、LogServiceProvider、RoutingServiceProvider三个服务提供者被生成了,具体的流程,可以参见聊一聊Laravel的核心----服务容器在这里就不做赘述了。

服务提供者两个最基本的操作分别就是register方法和boot方法,下面我们分别来看看这两个方法的作用。

入门概述

register方法

register方法就是让用户去处理服务「绑定」到服务容器的操作就可以了,如果您了解它的原理,你就应该知道「绑定」只是建立起接口和实现的映射关系,此时并不会去创建具体的实例,直到某个服务真的被用到的时候才会从服务容器中解析出来,而这个解析的过程是发生在所有服务绑定完成后。
对于复杂逻辑绑定,我们可以依靠「服务提供者」来进行操作,那么只是简单的服务绑定呢?我们还需要手动的去实现一个服务提供者类吗?答案是不需要的。

如果你的服务提供商注册许多简单的绑定,你可能想使用 bindings 和 singletons 属性而不是手动注册每个容器绑定。

class AppServiceProvider extends ServiceProvider
{
    /**
     * 设定容器绑定的对应关系
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * 设定单例模式的容器绑定对应关系
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
    ];
}

我们可以通过bingdings或singletons成员变量来进行简单的绑定

boot方法

我们知道register中并不能保证所有的服务已经被加载,所以当需要处理具有依赖关系的业务逻辑时,需要将业务逻辑放在boot中,这就是boot方法的作用。

在 boot 方法中我们可以去完成:注册事件监听器、引入路由文件、注册过滤器等任何你可以想象得到的业务处理。

配置服务提供者

了解完「服务提供者」两个重要方法后,我们还需要知道 Laravel 是如何查找到所有的服务提供者的。这个超找的过程就是去读取 config/app.php 文件中的 providers 数组内所有的「服务提供器」。

具体的读取过程我们也会在「服务提供者启动原理」一节中讲解。

延迟绑定服务提供者

当我们打开 config/app.php 配置文件时,你会发现有配置很多服务提供者,难道所有的都需要去执行它的 register 和 boot 方法么?

对于不会每次使用的服务提供者很明显,无需每次注册和启动,直到需要用到它的时候。

为了解决这个问题 Laravel 内置支持 延迟服务提供者 功能,启用时延迟功能后,当它真正需要注册绑定时才会执行 register 方法,这样就可以提升我们服务的性能了。

启用「延迟服务提供者」功能,需要完成两个操作配置:

在对应服务提供者中将 defer 属性设置为 true;
并定义 provides 方法,方法返回在提供者 register 方法内需要注册的服务接口名称。

我们可以查看config/app.php中声明的一个BroadcastServiceProvider作为说明:

class BroadcastServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(BroadcastManager::class, function ($app) {
            return new BroadcastManager($app);
        });

        $this->app->singleton(BroadcasterContract::class, function ($app) {
            return $app->make(BroadcastManager::class)->connection();
        });

        $this->app->alias(
            BroadcastManager::class, BroadcastingFactory::class
        );
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

服务提供者启动原理

引导程序的启动流程

服务提供者提供注册引导启动直到请求阶段才开始,我们从Http内核开始,进入到App\Http\Kernel::class,我们可以发现这个类继承自Illuminate\Foundation\Http\Kernel
在Illuminate\Foundation\Http\Kernel中,我们可以看到:

namespace Illuminate\Foundation\Http;

...

class Kernel implements KernelContract
{
    /**
     * The bootstrap classes for the application.
     * 应用引导类
     * @var array
     */
    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,

		//用于注册(register)「服务提供者」的引导类
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        // 用于启动(boot)「服务提供者」的引导类
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];
}

当一个HTTP请求到达时,在public/index.php中:

//从服务容器中解析处理 HTTP 请求的 Kernel 实例
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

//终止程序,做一些善后及清理工作
$kernel->terminate($request, $response);

可以看到从服务容器中解析出一个处理HTTP请求的Kernel实例(Illuminate\Contracts\Http\Kernel::class),之后调用「handle」方法来处理请求,那么我们来看一看Illuminate\Contracts\Http\Kernel::class下的「handle」方法。

    /**
     * 处理 HTTP 请求
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));

            $response = $this->renderException($request, $e);
        }

        $this->app['events']->dispatch(
            new Events\RequestHandled($request, $response)
        );

        return $response;
    }

    /**
     * 对 HTTP 请求执行中间件处理后再发送到指定路由。
     * Send the given request through the middleware / router.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

		// 1. 引导类引导启动。
        $this->bootstrap();

		// 2. 中间件及请求处理,生成响应并返回响应。
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

    /**
     * 接收 HTTP 请求时启动应用引导程序。
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) { //确定应用程序以前是否已启动
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

通过上面的过程我们了解到Illuminate\Contracts\Http\Kernel::class内核处理HTTP请求时进行如下两个步骤:

  1. 通过bootstrap()完成启动引导程序,其中包括所有服务提供者的注册和引导处理;
  2. 处理HTTP请求(涉及到中间件,路由的相关处理,在此不做赘述)

我们再进入到 Illuminate\Foundation\Application 容器中去看看bootstrapWith方法,看看容器是如何将引导类引导启动的。

    /**
     * 执行给定引导程序
     * Run the given array of bootstrap classes.
     *
     * @param  string[]  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

			// 从容器中解析出实例,然后调用实例的 bootstrap() 方法引导启动。 
            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

通过服务容器的 bootstrap() 方法引导启动时,将定义的在 Illuminate\Foundation\Http\Kerne 类中的应用引导类($bootstrappers)交由 Application 服务容器引导启动。其中与「服务提供者」有关的引导类为:
Illuminate\Foundation\Http\Kerne HTTP 内核通过 bootstrap() 方法引导启动时,实际由服务容器(Application)去完成引导启动的工作,并依据定义在 HTTP 内核中的引导类属性配置顺序依次引导启动,最终「服务提供者」的启动顺序是:

  • 执行「服务提供者」register 方法的引导类:\Illuminate\Foundation\Bootstrap\RegisterProviders::class,将完成所有定义在 config/app.php 配置中的服务提供者的注册(register)处理;
  • 执行「服务提供者」boot 方法的引导类:\Illuminate\Foundation\Bootstrap\BootProviders::class,将完成所有定义在 config/app.php 配置中的服务提供者的启动(boot)处理。
执行服务提供者注册(register)处理

通过上面的代码我们知道「服务提供者」的注册由 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 引导类启动方法(botstrap ())完成。
我们打开这个类:

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Contracts\Foundation\Application;

class RegisterProviders
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}

发现是通过调用服务容器的registerConfiguredProviders方法来完成引导启动。下面我们到服务容器的registerConfiguredProviders方法中一探究竟:

    /**
     * 执行所有配置服务提供者完成注册处理
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config['app.providers'])
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, 'Illuminate\\');
                        });

        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

		// 通过服务提供者仓库(ProviderRepository)加载所有的提供者。
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }

最后由服务提供者仓库(ProviderRepository)提供服务提供者的注册处理:

namespace Illuminate\Foundation;

use Exception;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class ProviderRepository
{
    public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
    {
        $this->app = $app;
        $this->files = $files;
        $this->manifestPath = $manifestPath;
    }

    /**
     * 注册应用的服务提供者。
     * Register the application service providers.
     *
     * @param  array  $providers
     * @return void
     */
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();

        // First we will load the service manifest, which contains information on all
        // service providers registered with the application and which services it
        // provides. This is used to know which services are "deferred" loaders.
        
        // 首先从服务提供者的缓存清单文件中载入服务提供者集合。其中包含「延迟加载」的服务提供者。
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }

        // Next, we will register events to load the providers for each of the events
        // that it has requested. This allows the service provider to defer itself
        // while still getting automatically loaded when a certain event occurs.
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        // We will go ahead and register all of the eagerly loaded providers with the
        // application so their services can be registered with the application as
        // a provided service. Then we will set the deferred service list on it.

		// 到这里,先执行应用必要(贪婪)的服务提供者完成服务注册。
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }

		// 最后将所有「延迟加载服务提供者」加入到容器中。
        $this->app->addDeferredServices($manifest['deferred']);
    }
}

	...
	
    /**
     * 将服务提供者编译到清单文件中缓存起来
     * Compile the application service manifest file.
     *
     * @param  array  $providers
     * @return array
     */
    protected function compileManifest($providers)
    {
        // The service manifest should contain a list of all of the providers for
        // the application so we can compare it on each request to the service
        // and determine if the manifest should be recompiled or is current.
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
        	// 解析出 $provider 对应的实例
            $instance = $this->createProvider($provider);

            // When recompiling the service manifest, we will spin through each of the
            // providers and check if it's a deferred provider or not. If so we'll
            // add it's provided services to the manifest and note the provider.
            
            // 判断当前服务提供者是否为「延迟加载」类行的,是则将其加入到缓存文件的「延迟加载(deferred)」集合中。
            if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }

                $manifest['when'][$provider] = $instance->when();
            }

            // If the service providers are not deferred, we will simply add it to an
            // array of eagerly loaded providers that will get registered on every
            // request to this application instead of "lazy" loading every time.

			// 如果不是「延迟加载」类型的服务提供者,则为贪婪加载必须立即去执行注册方法。
            else {
                $manifest['eager'][] = $provider;
            }
        }
		
		// 将归类后的服务提供者写入清单文件。
        return $this->writeManifest($manifest);
    }

在 服务提供者仓库(ProviderRepository) 处理程序中依次执行如下处理:

  • 如果存在服务提供者缓存清单,则直接读取「服务提供者」集合;
  • 否则,将从 config/app.php 配置中的服务提供者编译到缓存清单中;
    • 这个处理由 compileManifest() 方法完成;
    • 编译缓存清单时将处理贪婪加载(eager)和延迟加载(deferred)的服务提供者
  • 对于贪婪加载的提供者直接执行服务容器的 register 方法完成服务注册;
  • 将延迟加载提供者加入到服务容器中,按需注册和引导启动。

最后通过 Illuminate\Foundation\Application 容器完成注册处理(register)

执行服务提供者启动(boot)处理

其实「服务提供者」的启动流程和注册流程大致是相同的,我们可以看一下\Illuminate\Foundation\Bootstrap\BootProviders::class:
首先是BootProviders引导启动

class BootProviders
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $app->boot();
    }
}

可以发现其实是在服务容器中完成了服务提供者的引导启动逻辑:

    /**
     * 引导启动应用所有服务提供者
     * Boot the application's service providers.
     *
     * @return void
     */
    public function boot()
    {
        if ($this->isBooted()) {
            return;
        }

        // Once the application has booted we will also fire some "booted" callbacks
        // for any listeners that need to do work after this initial booting gets
        // finished. This is useful when ordering the boot-up processes we run.
        $this->fireAppCallbacks($this->bootingCallbacks);

		// 遍历并执行服务提供者的 boot 方法
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });

		// 指示应用程序已“启动”
        $this->booted = true;  

        $this->fireAppCallbacks($this->bootedCallbacks);
    }

    /**
     * 启动给定服务提供者
     * Boot the given service provider.
     *
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

以上便是服务提供者执行 注册绑定服务引导启动 的相关实现。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值