摘要:服務(wù)提供者啟動(dòng)原理之前我們有學(xué)習(xí)深度挖掘生命周期和深入剖析服務(wù)容器,今天我們將學(xué)習(xí)服務(wù)提供者。的所有核心服務(wù)都是通過(guò)服務(wù)提供者進(jìn)行引導(dǎo)啟動(dòng)的,所以想深入了解那么研究服務(wù)提供者的原理是個(gè)繞不開(kāi)的話(huà)題。
本文首發(fā)于 深入剖析 Laravel 服務(wù)提供者實(shí)現(xiàn)原理,轉(zhuǎn)載請(qǐng)注明出處。
今天我們將學(xué)習(xí) Laravel 框架另外一個(gè)核心內(nèi)容「服務(wù)提供者(Service Provider)」。服務(wù)提供者的功能是完成 Laravel 應(yīng)用的引導(dǎo)啟動(dòng),或者說(shuō)是將 Laravel 中的各種服務(wù)「注冊(cè)」到「Laravel 服務(wù)容器」,這樣才能在后續(xù)處理 HTTP 請(qǐng)求時(shí)使用這些服務(wù)。
服務(wù)提供者基本概念我們知道 「服務(wù)提供者」是配置應(yīng)用的中心,它的主要工作是使用「服務(wù)容器」實(shí)現(xiàn)服務(wù)容器綁定、事件監(jiān)聽(tīng)器、中間件,甚至是路由的注冊(cè)。
除核心服務(wù)外,幾乎所有的服務(wù)提供者都定義在配置文件 config/app.php 文件中的 providers 節(jié)點(diǎn)中。
服務(wù)提供者的典型處理流程是,當(dāng)接 Laravel 應(yīng)用接收到 HTTP 請(qǐng)求時(shí)會(huì)去執(zhí)行「服務(wù)提供者的 register(注冊(cè))」方法,將各個(gè)服務(wù)「綁定」到容器內(nèi);之后,到了實(shí)際處理請(qǐng)求階段,依據(jù)使用情況按需加載所需服務(wù)。這樣的優(yōu)勢(shì)很明顯能夠提升應(yīng)用的性能。
細(xì)心的朋友可能發(fā)現(xiàn)這里用了一個(gè)詞「幾乎」,沒(méi)錯(cuò)還有一些屬于核心服務(wù)提供者,這些并沒(méi)有定義在 providers 配置節(jié)點(diǎn)中而是直接由 IlluminateFoundationApplication 服務(wù)容器直接在實(shí)例化階段就完成了注冊(cè)服務(wù)。
registerBaseServiceProviders(); ... } /** * Register all of the base service providers. 注冊(cè)應(yīng)用基礎(chǔ)服務(wù)提供者 * * @return void */ protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); }
對(duì)服務(wù)容器不是很熟悉的老鐵可以閱讀 深入剖析 Laravel 服務(wù)容器,并且在文中「注冊(cè)基礎(chǔ)服務(wù)提供者」一節(jié)也有詳細(xì)分析服務(wù)容器是如何注冊(cè)服務(wù)提供者的。
另外一個(gè),我們還需要了解的是所有的服務(wù)提供者都繼承自 IlluminateSupportServiceProvider 類(lèi)。不過(guò)對(duì)于我們來(lái)說(shuō)目前還無(wú)需研究基類(lèi),所以我們將焦點(diǎn)放到如何實(shí)現(xiàn)一個(gè)自定義的服務(wù)提供者,然后還有兩個(gè)需要掌握方法。
服務(wù)提供者入門(mén) 創(chuàng)建自定義服務(wù)提供者要?jiǎng)?chuàng)建自定義的「服務(wù)提供者」,可以直接使用 Laravel 內(nèi)置的 artisan 命令完成。
php artisan make:provider RiskServiceProvider
這個(gè)命令會(huì)在 app/Providers 目錄下創(chuàng)建 RiskServiceProvider.php 文件,打開(kāi)文件內(nèi)容如下:
register 方法在 register 方法中,我們無(wú)需處理業(yè)務(wù)邏輯,在這個(gè)方法中你只需去處理「綁定」服務(wù)到服務(wù)容器中即可。
從文檔中我們知道:
在 register 方法中,你只需要將類(lèi)綁定到 服務(wù)容器 中。而不需要嘗試在 register 方法中注冊(cè)任何事件監(jiān)聽(tīng)器、路由或者任何其他功能。否則,你可能會(huì)意外使用到尚未加載的服務(wù)提供器提供的服務(wù)。如何理解這句話(huà)的含義呢?
如果你有了解過(guò)服務(wù)容器運(yùn)行原理,就會(huì)知道在「綁定」操作僅僅是建立起接口和實(shí)現(xiàn)的對(duì)應(yīng)關(guān)系,此時(shí)并不會(huì)創(chuàng)建具體的實(shí)例,即不會(huì)存在真實(shí)的依賴(lài)關(guān)系。直到某個(gè)服務(wù)真的被用到時(shí)才會(huì)從「服務(wù)容器」中解析出來(lái),而解析的過(guò)程發(fā)生在所有服務(wù)「注冊(cè)」完成之后。
一旦我們嘗試在 register 注冊(cè)階段使用某些未被加載的服務(wù)依賴(lài),即這個(gè)服務(wù)目前還沒(méi)有被注冊(cè)所以不可用。
這樣就需要在「注冊(cè)」綁定時(shí),同時(shí)需要關(guān)注服務(wù)的注冊(cè)順序,但這一點(diǎn) Laravel 并不作出任何保證。
理解了這個(gè)道理,我們就可以隨便進(jìn)入一個(gè)「服務(wù)提供者」來(lái)看看其中的 register 方法的邏輯,現(xiàn)在我們挑選的是 IlluminateCacheCacheServiceProvider 服務(wù)作為講解:
app->singleton("cache", function ($app) { return new CacheManager($app); }); $this->app->singleton("cache.store", function ($app) { return $app["cache"]->driver(); }); $this->app->singleton("memcached.connector", function () { return new MemcachedConnector; }); } /** * Get the services provided by the provider. * * @return array */ public function provides() { return [ "cache", "cache.store", "memcached.connector", ]; } }沒(méi)錯(cuò),如你所預(yù)料的一樣,它的 register 方法執(zhí)行了三個(gè)單例綁定操作,僅此而已。
簡(jiǎn)單注冊(cè)服務(wù)對(duì)于處理復(fù)雜綁定邏輯,可以自定義「服務(wù)提供者」。但是如果是比較簡(jiǎn)單的注冊(cè)服務(wù),有沒(méi)有比較方便的綁定方法呢?畢竟,并不是每個(gè)服務(wù)都會(huì)有復(fù)雜的依賴(lài)處理。
我們可以從 文檔 中得到解答:
如果你的服務(wù)提供商注冊(cè)許多簡(jiǎn)單的綁定,你可能想使用 bindings 和 singletons 屬性而不是手動(dòng)注冊(cè)每個(gè)容器綁定。DigitalOceanServerProvider::class, ]; /** * 設(shè)定單例模式的容器綁定對(duì)應(yīng)關(guān)系 * * @var array */ public $singletons = [ DowntimeNotifier::class => PingdomDowntimeNotifier::class, ]; }此時(shí),通過(guò) bingdings 或 singletons 成員變量來(lái)設(shè)置簡(jiǎn)單的綁定,就可以避免大量的「服務(wù)提供者」類(lèi)的生成了。
boot 方法聊完了 register 方法,接下來(lái)進(jìn)入另一個(gè)主題,來(lái)研究一下服務(wù)提供者的 boot 方法。
通過(guò)前面的學(xué)習(xí),我們知道在 register 方法中 Laravel 并不能保證所有其他服務(wù)已被加載。所以當(dāng)需要處理具有依賴(lài)關(guān)系的業(yè)務(wù)邏輯時(shí),應(yīng)該將這些邏輯處理放置到 boot 方法內(nèi)。在 boot 方法中我們可以去完成:注冊(cè)事件監(jiān)聽(tīng)器、引入路由文件、注冊(cè)過(guò)濾器等任何你可以想象得到的業(yè)務(wù)處理。
在 config/app.php 配置中我們可以看到如下幾個(gè)服務(wù)提供者:
/* * Application Service Providers... */ AppProvidersAppServiceProvider::class, AppProvidersAuthServiceProvider::class, // AppProvidersBroadcastServiceProvider::class, AppProvidersEventServiceProvider::class, AppProvidersRouteServiceProvider::class,選擇其中的 AppProvidersRouteServiceProvider::class 服務(wù)提供者它繼承自 IlluminateFoundationSupportProvidersRouteServiceProvider 基類(lèi)來(lái)看下:
// 實(shí)現(xiàn)類(lèi) class RouteServiceProvider extends ServiceProvider { /** * This namespace is applied to your controller routes. In addition, it is set as the URL generator"s root namespace. */ protected $namespace = "AppHttpControllers"; /** * Define your route model bindings, pattern filters, etc. */ public function boot() { parent::boot(); } /** * Define the routes for the application. 定義應(yīng)用路由 */ public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); } /** * Define the "web" routes for the application. These routes all receive session state, CSRF protection, etc. * 定義 web 路由。web 路由支持會(huì)話(huà)狀態(tài)和 CSRF 防御中間件等。 */ protected function mapWebRoutes() { Route::middleware("web") ->namespace($this->namespace) ->group(base_path("routes/web.php")); } /** * Define the "api" routes for the application. These routes are typically stateless. * 定義 api 路由。api 接口路由支持典型的 HTTP 無(wú)狀態(tài)協(xié)議。 */ protected function mapApiRoutes() { Route::prefix("api") ->middleware("api") ->namespace($this->namespace) ->group(base_path("routes/api.php")); } }基類(lèi) IlluminateFoundationSupportProvidersRouteServiceProvider:
// 基類(lèi) namespace IlluminateFoundationSupportProviders; /** * @mixin IlluminateRoutingRouter */ class RouteServiceProvider extends ServiceProvider { /** * The controller namespace for the application. */ protected $namespace; /** * Bootstrap any application services. 引導(dǎo)啟動(dòng)服務(wù) */ public function boot() { $this->setRootControllerNamespace(); // 如果已緩存路由,從緩存文件中載入路由 if ($this->app->routesAreCached()) { $this->loadCachedRoutes(); } else { //還沒(méi)有路由緩存,加載路由 $this->loadRoutes(); $this->app->booted(function () { $this->app["router"]->getRoutes()->refreshNameLookups(); $this->app["router"]->getRoutes()->refreshActionLookups(); }); } } /** * Load the application routes. 加載應(yīng)用路由,調(diào)用實(shí)例的 map 方法,該方法定義在 AppProvidersRouteServiceProvider::class 中。 */ protected function loadRoutes() { if (method_exists($this, "map")) { $this->app->call([$this, "map"]); } } }對(duì)于 RouteServiceProvider 來(lái)講,它的 boot 方法在處理一個(gè)路由載入的問(wèn)題:
判斷是否已有路由緩存;
有路由緩存,則直接載入路由緩存;
無(wú)路由緩存,執(zhí)行 map 方法載入路由。
感興趣的朋友可以自行了解下 Application Service Providers 配置節(jié)點(diǎn)的相關(guān)服務(wù)提供者,這邊不再贅述。
配置服務(wù)提供者了解完「服務(wù)提供者」兩個(gè)重要方法后,我們還需要知道 Laravel 是如何查找到所有的服務(wù)提供者的。這個(gè)超找的過(guò)程就是去讀取 config/app.php 文件中的 providers 節(jié)點(diǎn)內(nèi)所有的「服務(wù)提供器」。
具體的讀取過(guò)程我們也會(huì)在「服務(wù)提供者啟動(dòng)原理」一節(jié)中講解。
延遲綁定服務(wù)提供者對(duì)于一個(gè)項(xiàng)目來(lái)說(shuō),除了要讓它跑起來(lái),往往我們還需要關(guān)注它的性能問(wèn)題。
當(dāng)我們打開(kāi) config/app.php 配置文件時(shí),你會(huì)發(fā)現(xiàn)有配置很多服務(wù)提供者,難道所有的都需要去執(zhí)行它的 register 和 boot 方法么?
對(duì)于不會(huì)每次使用的服務(wù)提供者很明顯,無(wú)需每次注冊(cè)和啟動(dòng),直到需要用到它的時(shí)候。
為了解決這個(gè)問(wèn)題 Laravel 內(nèi)置支持 延遲服務(wù)提供者 功能,啟用時(shí)延遲功能后,當(dāng)它真正需要注冊(cè)綁定時(shí)才會(huì)執(zhí)行 register 方法,這樣就可以提升我們服務(wù)的性能了。
啟用「延遲服務(wù)提供者」功能,需要完成兩個(gè)操作配置:
在對(duì)應(yīng)服務(wù)提供者中將 defer 屬性設(shè)置為 true;
并定義 provides 方法,方法返回在提供者 register 方法內(nèi)需要注冊(cè)的服務(wù)接口名稱(chēng)。
我們拿 config/app.php 配置中的 BroadcastServiceProvider 作為演示說(shuō)明:
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. 獲取提供者所提供的服務(wù)接口名稱(chēng)。 */ public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }小結(jié)在「服務(wù)提供者入門(mén)」這個(gè)小節(jié)我們學(xué)習(xí)了服務(wù)提供者的基本使用和性能優(yōu)化相關(guān)知識(shí),包括:
如何創(chuàng)建自定義的服務(wù)提供者;
創(chuàng)建 register 方法注冊(cè)服務(wù)到 Laravel 服務(wù)容器;
創(chuàng)建 boot 方法啟動(dòng)服務(wù)提供者的引導(dǎo)程序;
配置我們的服務(wù)提供者到 config/app.php 文件,這樣才能在容器中加載相應(yīng)服務(wù);
通過(guò)延遲綁定技術(shù),提升 Laravel 服務(wù)性能。
下一小節(jié),我們將焦點(diǎn)轉(zhuǎn)移到「服務(wù)提供者」的實(shí)現(xiàn)原理中,深入到 Laravel 內(nèi)核中去探索「服務(wù)提供者」如何被注冊(cè)和啟動(dòng),又是如何能夠通過(guò)延遲技術(shù)提升 Laravel 應(yīng)用的性能的。
服務(wù)提供者啟動(dòng)原理之前我們有學(xué)習(xí) 深度挖掘 Laravel 生命周期 和 深入剖析 Laravel 服務(wù)容器,今天我們將學(xué)習(xí)「服務(wù)提供者」。
Laravel 的所有核心服務(wù)都是通過(guò)服務(wù)提供者進(jìn)行引導(dǎo)啟動(dòng)的,所以想深入了解 Laravel 那么研究「服務(wù)提供者」的原理是個(gè)繞不開(kāi)的話(huà)題。
引導(dǎo)程序的啟動(dòng)流程服務(wù)提供者 注冊(cè) 和 引導(dǎo)啟動(dòng) 直到處理 HTTP 請(qǐng)求階段才開(kāi)始。所以我們直接進(jìn)入到 AppConsoleKernel::class 類(lèi),同時(shí)這個(gè)類(lèi)繼承于 IlluminateFoundationHttpKernel 類(lèi)。
從 IlluminateFoundationHttpKernel 類(lèi)中我們可以看到如下內(nèi)容:
class Kernel implements KernelContract { ... /** * The bootstrap classes for the application. 應(yīng)用引導(dǎo)類(lèi) */ protected $bootstrappers = [ ... IlluminateFoundationBootstrapRegisterProviders::class, // 用于注冊(cè)(register)「服務(wù)提供者」的引導(dǎo)類(lèi) IlluminateFoundationBootstrapBootProviders::class, // 用于啟動(dòng)(boot)「服務(wù)提供者」的引導(dǎo)類(lèi) ]; /** * Handle an incoming HTTP request. 處理 HTTP 請(qǐng)求 */ public function handle($request) { try { $request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request); } catch (Exception $e) { ... } catch (Throwable $e) { ... } ... } /** * Send the given request through the middleware / router. 對(duì) HTTP 請(qǐng)求執(zhí)行中間件處理后再發(fā)送到指定路由。 */ protected function sendRequestThroughRouter($request) { ... // 1. 引導(dǎo)類(lèi)引導(dǎo)啟動(dòng)。 $this->bootstrap(); // 2. 中間件及請(qǐng)求處理,生成響應(yīng)并返回響應(yīng)。 return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } /** * Bootstrap the application for HTTP requests. 接收 HTTP 請(qǐng)求時(shí)啟動(dòng)應(yīng)用引導(dǎo)程序。 */ public function bootstrap() { // 引導(dǎo)類(lèi)啟動(dòng)由 Application 容器引導(dǎo)啟動(dòng)。 if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } } }在 IlluminateFoundationHttpKernel 我們的內(nèi)核處理 HTTP 請(qǐng)求時(shí)會(huì)經(jīng)過(guò)一下兩個(gè)主要步驟:
啟動(dòng)引導(dǎo)程序通過(guò) $this->bootstrap() 方法完成,其中包括所有服務(wù)提供者的注冊(cè)和引導(dǎo)處理;
處理 HTTP 請(qǐng)求(這個(gè)問(wèn)題涉及到中間件、路由及相應(yīng)處理,本文將不做深入探討)。
進(jìn)入 IlluminateFoundationApplication 容器中的 bootstrapWith() 方法,來(lái)看看容器是如何將引導(dǎo)類(lèi)引導(dǎo)啟動(dòng)的:
/** * Run the given array of bootstrap classes. 執(zhí)行給定引導(dǎo)程序 */ public function bootstrapWith(array $bootstrappers) { $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { $this["events"]->fire("bootstrapping: ".$bootstrapper, [$this]); // 從容器中解析出實(shí)例,然后調(diào)用實(shí)例的 bootstrap() 方法引導(dǎo)啟動(dòng)。 $this->make($bootstrapper)->bootstrap($this); $this["events"]->fire("bootstrapped: ".$bootstrapper, [$this]); } }通過(guò)服務(wù)容器的 bootstrap() 方法引導(dǎo)啟動(dòng)時(shí),將定義的在 IlluminateFoundationHttpKerne 類(lèi)中的應(yīng)用引導(dǎo)類(lèi)($bootstrappers)交由 Application 服務(wù)容器引導(dǎo)啟動(dòng)。其中與「服務(wù)提供者」有關(guān)的引導(dǎo)類(lèi)為:
當(dāng) IlluminateFoundationHttpKerne HTTP 內(nèi)核通過(guò) bootstrap() 方法引導(dǎo)啟動(dòng)時(shí),實(shí)際由服務(wù)容器(Application)去完成引導(dǎo)啟動(dòng)的工作,并依據(jù)定義在 HTTP 內(nèi)核中的引導(dǎo)類(lèi)屬性配置順序依次引導(dǎo)啟動(dòng),最終「服務(wù)提供者」的啟動(dòng)順序是:
執(zhí)行「服務(wù)提供者」register 方法的引導(dǎo)類(lèi):IlluminateFoundationBootstrapRegisterProviders::class,將完成所有定義在 config/app.php 配置中的服務(wù)提供者的注冊(cè)(register)處理;
執(zhí)行「服務(wù)提供者」boot 方法的引導(dǎo)類(lèi):IlluminateFoundationBootstrapBootProviders::class,將完成所有定義在 config/app.php 配置中的服務(wù)提供者的啟動(dòng)(boot)處理。
Laravel 執(zhí)行服務(wù)提供者注冊(cè)(register)處理前面說(shuō)過(guò)「服務(wù)提供者」的注冊(cè)由 IlluminateFoundationBootstrapRegisterProviders::class 引導(dǎo)類(lèi)啟動(dòng)方法(botstrap())完成。
1. RegisterProviders 引導(dǎo)注冊(cè)
registerConfiguredProviders(); } }在其通過(guò)調(diào)用服務(wù)容器的 registerConfiguredProviders() 方法完成引導(dǎo)啟動(dòng),所以我們需要到容器中一探究竟。
2. 由服務(wù)容器執(zhí)行配置文件中的所有服務(wù)提供者服務(wù)完成注冊(cè)。
/** * Register all of the configured providers. 執(zhí)行所有配置服務(wù)提供者完成注冊(cè)處理。 * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php */ 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()]); // 通過(guò)服務(wù)提供者倉(cāng)庫(kù)(ProviderRepository)加載所有的提供者。 (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray()); }3. 最后由服務(wù)提供者倉(cāng)庫(kù)(ProviderRepository)執(zhí)行服務(wù)提供者的注冊(cè)處理。
loadManifest(); // 首先從服務(wù)提供者的緩存清單文件中載入服務(wù)提供者集合。其中包含「延遲加載」的服務(wù)提供者。 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); } // 到這里,先執(zhí)行應(yīng)用必要(貪婪)的服務(wù)提供者完成服務(wù)注冊(cè)。 foreach ($manifest["eager"] as $provider) { $this->app->register($provider); } // 最后將所有「延遲加載服務(wù)提供者」加入到容器中。 $this->app->addDeferredServices($manifest["deferred"]); } /** * Compile the application service manifest file. 將服務(wù)提供者編譯到清單文件中緩存起來(lái)。 */ 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 對(duì)應(yīng)的實(shí)例 $instance = $this->createProvider($provider); // 判斷當(dāng)前服務(wù)提供者是否為「延遲加載」類(lèi)行的,是則將其加入到緩存文件的「延遲加載(deferred)」集合中。 if ($instance->isDeferred()) { foreach ($instance->provides() as $service) { $manifest["deferred"][$service] = $provider; } $manifest["when"][$provider] = $instance->when(); } // 如果不是「延遲加載」類(lèi)型的服務(wù)提供者,則為貪婪加載必須立即去執(zhí)行注冊(cè)方法。 else { $manifest["eager"][] = $provider; } } // 將歸類(lèi)后的服務(wù)提供者寫(xiě)入清單文件。 return $this->writeManifest($manifest); }在 服務(wù)提供者倉(cāng)庫(kù)(ProviderRepository) 處理程序中依次執(zhí)行如下處理:
如果存在服務(wù)提供者緩存清單,則直接讀取「服務(wù)提供者」集合;
否則,將從 config/app.php 配置中的服務(wù)提供者編譯到緩存清單中;編譯由 compileManifest() 方法完成; 編譯緩存清單時(shí)將處理貪婪加載(eager)和延遲加載(deferred)的服務(wù)提供者;
對(duì)于貪婪加載的提供者直接執(zhí)行服務(wù)容器的 register 方法完成服務(wù)注冊(cè);
將延遲加載提供者加入到服務(wù)容器中,按需注冊(cè)和引導(dǎo)啟動(dòng)。
最后通過(guò) IlluminateFoundationApplication 容器完成注冊(cè)處理:
/** * Register a service provider with the application. 在應(yīng)用服務(wù)容器中注冊(cè)一個(gè)服務(wù)提供者。 */ public function register($provider, $options = [], $force = false) { if (($registered = $this->getProvider($provider)) && ! $force) { return $registered; } // 如果給定的服務(wù)提供者是接口名稱(chēng),解析出它的實(shí)例。 if (is_string($provider)) { $provider = $this->resolveProvider($provider); } // 服務(wù)提供者提供注冊(cè)方法時(shí),執(zhí)行注冊(cè)服務(wù)處理 if (method_exists($provider, "register")) { $provider->register(); } $this->markAsRegistered($provider); // 判斷 Laravel 應(yīng)用是否已啟動(dòng)。已啟動(dòng)的話(huà)需要去執(zhí)行啟動(dòng)處理。 if ($this->booted) { $this->bootProvider($provider); } return $provider; }為什么需要判斷是否已經(jīng)啟動(dòng)過(guò)呢?
因?yàn)閷?duì)于延遲加載的服務(wù)提供者只有在使用時(shí)才會(huì)被調(diào)用,所以這里需要這樣判斷,然后再去啟動(dòng)它。
以上,便是
Laravel 執(zhí)行服務(wù)提供者啟動(dòng)(boot)處理「服務(wù)提供者」的啟動(dòng)流程和注冊(cè)流程大致相同,有興趣的朋友可以深入源碼了解一下。
1. BootProviders 引導(dǎo)啟動(dòng)
boot(); } }2. 由服務(wù)容器執(zhí)行配置文件中的所有服務(wù)提供者服務(wù)完成啟動(dòng)。
/** * Boot the application"s service providers. 引導(dǎo)啟動(dòng)應(yīng)用所有服務(wù)提供者 * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php */ public function boot() { if ($this->booted) { 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); // 遍歷并執(zhí)行服務(wù)提供者的 boot 方法。 array_walk($this->serviceProviders, function ($p) { $this->bootProvider($p); }); $this->booted = true; $this->fireAppCallbacks($this->bootedCallbacks); } /** * Boot the given service provider. 啟動(dòng)給定服務(wù)提供者 */ protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, "boot")) { return $this->call([$provider, "boot"]); } }以上便是服務(wù)提供者執(zhí)行 注冊(cè)綁定服務(wù) 和 引導(dǎo)啟動(dòng) 的相關(guān)實(shí)現(xiàn)。
但是稍等一下,我們是不是忘記了還有「延遲加載」類(lèi)型的服務(wù)提供者,它們還沒(méi)有被注冊(cè)和引導(dǎo)啟動(dòng)呢!
Laravel 如何完成延遲加載類(lèi)型的服務(wù)提供者對(duì)于延遲加載類(lèi)型的服務(wù)提供者,我們要到使用時(shí)才會(huì)去執(zhí)行它們內(nèi)部的 register 和 boot 方法。這里我們所說(shuō)的使用即使需要 解析 它,我們知道解析處理由服務(wù)容器完成。
所以我們需要進(jìn)入到 IlluminateFoundationApplication 容器中探索 make 解析的一些細(xì)節(jié)。
/** * Resolve the given type from the container. 從容器中解析出給定服務(wù) * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php */ public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($abstract); // 判斷這個(gè)接口是否為延遲類(lèi)型的并且沒(méi)有被解析過(guò),是則去將它加載到容器中。 if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) { $this->loadDeferredProvider($abstract); } return parent::make($abstract, $parameters); } /** * Load the provider for a deferred service. 加載給定延遲加載服務(wù)提供者 */ public function loadDeferredProvider($service) { if (! isset($this->deferredServices[$service])) { return; } $provider = $this->deferredServices[$service]; // 如果服務(wù)為注冊(cè)則去注冊(cè)并從延遲服務(wù)提供者集合中刪除它。 if (! isset($this->loadedProviders[$provider])) { $this->registerDeferredProvider($provider, $service); } } /** * Register a deferred provider and service. 去執(zhí)行服務(wù)提供者的注冊(cè)方法。 */ public function registerDeferredProvider($provider, $service = null) { // Once the provider that provides the deferred service has been registered we // will remove it from our local list of the deferred services with related // providers so that this container does not try to resolve it out again. if ($service) { unset($this->deferredServices[$service]); } // 執(zhí)行服務(wù)提供者注冊(cè)服務(wù)。 $this->register($instance = new $provider($this)); // 執(zhí)行服務(wù)提供者啟動(dòng)服務(wù)。 if (! $this->booted) { $this->booting(function () use ($instance) { $this->bootProvider($instance); }); } }總結(jié)今天我們深入研究了 Laravel 服務(wù)提供者的注冊(cè)和啟動(dòng)的實(shí)現(xiàn)原理,希望對(duì)大家有所幫助。
如果對(duì)如何自定義服務(wù)提供者不甚了解的朋友可以去閱讀 Laravel 服務(wù)提供者指南 這篇文章。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/28788.html
摘要:劃下重點(diǎn),服務(wù)容器是用于管理類(lèi)的依賴(lài)和執(zhí)行依賴(lài)注入的工具。類(lèi)的實(shí)例化及其依賴(lài)的注入,完全由服務(wù)容器自動(dòng)的去完成。 本文首發(fā)于 深入剖析 Laravel 服務(wù)容器,轉(zhuǎn)載請(qǐng)注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。 之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請(qǐng)求,又是如何生成響應(yīng)并最終呈現(xiàn)給用戶(hù)的工作原理。 本章將帶領(lǐng)大...
摘要:外觀模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。將使用者與子系統(tǒng)從直接耦合,轉(zhuǎn)變成由外觀類(lèi)提供統(tǒng)一的接口給使用者使用,以降低客戶(hù)端與子系統(tǒng)之間的耦合度。接下來(lái)將深入分析外觀服務(wù)的加載過(guò)程。引導(dǎo)程序?qū)⒃谔幚碚?qǐng)求是完成引導(dǎo)啟動(dòng)。 本文首發(fā)于 深入淺出 Laravel 的 Facade 外觀系統(tǒng),轉(zhuǎn)載請(qǐng)注明出處。 今天我們將學(xué)習(xí) Laravel 核心架構(gòu)中的另一個(gè)主題「Fac...
摘要:作者鏈接來(lái)源簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。同時(shí)順手整理個(gè)人對(duì)源碼的相關(guān)理解,希望能夠稍微填補(bǔ)學(xué)習(xí)領(lǐng)域的空白。系列文章只會(huì)節(jié)選關(guān)鍵代碼輔以思路講解,請(qǐng)自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swoft...
摘要:阻塞,非阻塞首先,阻塞這個(gè)詞來(lái)自操作系統(tǒng)的線(xiàn)程進(jìn)程的狀態(tài)模型網(wǎng)絡(luò)爬蟲(chóng)基本原理一后端掘金網(wǎng)絡(luò)爬蟲(chóng)是捜索引擎抓取系統(tǒng)的重要組成部分。每門(mén)主要編程語(yǔ)言現(xiàn)未來(lái)已到后端掘金使用和在相同環(huán)境各加載多張小圖片,性能相差一倍。 2016 年度小結(jié)(服務(wù)器端方向)| 掘金技術(shù)征文 - 后端 - 掘金今年年初我花了三個(gè)月的業(yè)余時(shí)間用 Laravel 開(kāi)發(fā)了一個(gè)項(xiàng)目,在此之前,除了去年換工作準(zhǔn)備面試時(shí),我并...
摘要:阻塞,非阻塞首先,阻塞這個(gè)詞來(lái)自操作系統(tǒng)的線(xiàn)程進(jìn)程的狀態(tài)模型網(wǎng)絡(luò)爬蟲(chóng)基本原理一后端掘金網(wǎng)絡(luò)爬蟲(chóng)是捜索引擎抓取系統(tǒng)的重要組成部分。每門(mén)主要編程語(yǔ)言現(xiàn)未來(lái)已到后端掘金使用和在相同環(huán)境各加載多張小圖片,性能相差一倍。 2016 年度小結(jié)(服務(wù)器端方向)| 掘金技術(shù)征文 - 后端 - 掘金今年年初我花了三個(gè)月的業(yè)余時(shí)間用 Laravel 開(kāi)發(fā)了一個(gè)項(xiàng)目,在此之前,除了去年換工作準(zhǔn)備面試時(shí),我并...
閱讀 1466·2021-09-02 13:57
閱讀 1882·2019-08-30 15:55
閱讀 2419·2019-08-30 15:54
閱讀 2260·2019-08-30 15:44
閱讀 2741·2019-08-30 13:18
閱讀 491·2019-08-30 13:02
閱讀 660·2019-08-29 18:46
閱讀 1673·2019-08-29 11:25