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
为什么需要延迟加载?#
一些不常用的服务,不需要每次都准备好,生意可以设置为当需要的时候才去加载解析服务实例,这样可以提升框架加载性能,节省内存。
何时触发延迟加载的服务?#
需要用到这个服务的时候
如何触发延迟加载的服务?#
- app (xxx) 直接 make 解析出实例,大部分时候应该也是用这种方法
- 事件监听注册服务
主要聊聊这个事件监听注册服务的方法,之前看到 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 协议》,转载必须注明作者和本文链接
推荐文章: