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 协议》,转载必须注明作者和本文链接
提前写好的吗?一下子7篇 :scream:
@Everan :joy: