Laravel 源码学习笔记 7:服务提供者-延迟加载的触发

之前有[laravel源码学习笔记4:kernel实例解析request请求]文章有稍微对服务提供者过了一遍,在启动容器的时候,会去根据config的providers来获取全部的服务提供者,然后会对比缓存文件cache/services.php来决定哪些是需要立即注册的服务,哪些是延迟加载的。
cache/services.php文件中,返回了一个数组,大概内容是这样

<?php return array (
  'providers' => 
  array (
    0 => 'Illuminate\\Auth\\AuthServiceProvider',
    1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    2 => 'Illuminate\\Bus\\BusServiceProvider',
    ...
  ),
  'eager' => 
  array (
    0 => 'Illuminate\\Auth\\AuthServiceProvider',
    1 => 'Illuminate\\Cookie\\CookieServiceProvider',
    ...
  ),
  'deferred' => 
  array (
    'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    ...
  ),
  'when' => 
  array (
    'Illuminate\\Broadcasting\\BroadcastServiceProvider' => array (

    ),
  )
);

数据比较多,就先注释了一部分,可以看到返回了四个键,分别是

  • providers(全部的服务提供者)
  • eager(框架启动立即注册的服务)
  • deferred(延迟加载的服务)
  • when(触发延迟加载的监听事件)

其中eager立即注册,也就是添加实现依赖,没啥好说,主要来聊聊延迟加载的服务deferred

为什么需要延迟加载?

一些不常用的服务,不需要每次都准备好,生意可以设置为当需要的时候才去加载解析服务实例,这样可以提升框架加载性能,节省内存。

何时触发延迟加载的服务?

需要用到这个服务的时候

如何触发延迟加载的服务?
  1. app(xxx)直接make解析出实例,大部分时候应该也是用这种方法
  2. 事件监听注册服务

主要聊聊这个事件监听注册服务的方法,之前看到when个数组的时候,就很好奇,这个到底是干嘛的,因为和eager还有deferred放在同一层数组上了,以为也是个加载服务的形式,后来看了里面服务对应了延迟加载里的服务,就猜测可能是用来触发延迟加载的,看代码

ProviderRepository.php
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.
        // 是否重新载入provider缓存文件
        if ($this->shouldRecompile($manifest, $providers)) {
            //重载后的的里面文件的数组信息
            $manifest = $this->compileManifest($providers);
        }
//        dd($manifest);
        // 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..
        // eager立即加载的服务
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }
        //把需要延迟加载的服务添加进app容器的deferredServices数组
        $this->app->addDeferredServices($manifest['deferred']);
    }

在注册服务的时候会执行load方法,里面的变量$manifest会根据不同键来做不同的服务处理,当有when的时候

foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }
protected function registerLoadEvents($provider, array $events)
    {
        Log::info('$provider'.json_encode([$provider]),[$events]);
        if (count($events) < 1) {
            return;
        }
        Log::info('cout event > 1 '.json_encode([$provider]),[$events]);
        $this->app->make('events')->listen($events, function () use ($provider) {
            Log::info('registerLoadEvents-register',[$provider]);
            $this->app->register($provider);
        });
    }

里面会去遍历服务对面的array里的内容,然后去做一个监听,然鹅我们看上面贴的services.php 里的when数组,对应的值都是空的,也就是,这里他不会去注册任何的监听,那when下的服务对应的数组里的内容是哪里来的呢?,我们看之前的compileManifest方法

if ($this->shouldRecompile($manifest, $providers)) {
            //重载后的的里面文件的数组信息
            $manifest = $this->compileManifest($providers);
        }
         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);

//        dd($providers);
        // Illuminate\Broadcasting\BroadcastServiceProvider
        foreach ($providers as $provider) {
            $instance = $this->createProvider($provider);
            if ($provider == 'Illuminate\Broadcasting\BroadcastServiceProvider'){
//                dd($instance,$instance->provides());
            }
            // 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.
            // 看provider里的defer 是否为true,是就是延迟加载
            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);
    }

compileManifest方法中,会根据传进去的providers数组,组装$manifest变量的内容

if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }

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

可以看到会根据$instance->isDeferred()这个实例的defer属性,如果是true就会丢进deferred数组里,下面还有一行

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

这个应该就是赋予when下服务对应的array数组的值,再去创建对应的监听了,那会发现$instance->when()这个方法哪里来呢?我们随便找之前services.php的文件里的一个延迟服务Illuminate\Broadcasting\BroadcastServiceProvider
里面的内容是

class BroadcastServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {

        Log::info('BroadcastServiceProvider-regist');
        $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,
        ];
    }
)

这里有绑定的方法,provides方法返回三个不同的实现类,那when方法在哪呢?我们可以看到这个provider继承的是ServiceProvider点进去发现了when方法!

/**
     * Get the events that trigger this service provider to register.
     *
     * @return array
     */
    public function when()
    {
        return [];
    }

看注释可以知道,这个是为了触发服务注册的,那我们在BroadcastServiceProvider中重写这个不就可以了?试试

class BroadcastServiceProvider extends ServiceProvider
{

    protected $defer = true;

    public function register()
    {
        ...
    }

    public function provides()
    {
        ...
    }

    public function when()
    {
        Log::info('BroadcastServiceProvider-when');
//        return [];
        return ['BroadcastServiceProvider-event'];
    }
}

我们返回了一个数组,里面算是我们自定义的监听名称,然后我们再随便再对框架进行个请求(http://blog.test/test)发现services.php 有东西了!

'when' => 
  array (
    'Illuminate\\Broadcasting\\BroadcastServiceProvider' => 
    array (
      0 => 'BroadcastServiceProvider-event',
    ),

也就是,在上面的registerLoadEvents方法中,我们应该注册了这个服务的监听!
这时候我们在控制器中测试下

class TestController extends Controller
{

    //
    public function test(){
//        $cache = App::make(Cache::class);

        Event::fire('BroadcastServiceProvider-event'); //注册进容器,单例绑定
dd(\app();
        $bm1 = App::make(BroadcastManager::class); // 注册容器后,绑定后,返回这个单例的实例
        $bm2 = App::make(BroadcasterContract::class); // 注册容器后,绑定后,返回这个单例的实例

        dd($bm2);
    }

如控制器所示,发送请求后,用event的fire方法监听,这时候dd容器,会发现里面就会有BroadcastServiceProvider,和下面的app直接make解析,也会注册,感兴趣的可以自己试下~

有什么不对或有疑问的地方请大佬们指正:)

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2
Jennie

提前写好的吗?一下子7篇 :scream:

4年前 评论
bestcyt

@Everan :joy:

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!