摘要:劃下重點(diǎn),服務(wù)容器是用于管理類的依賴和執(zhí)行依賴注入的工具。類的實(shí)例化及其依賴的注入,完全由服務(wù)容器自動(dòng)的去完成。
本文首發(fā)于 深入剖析 Laravel 服務(wù)容器,轉(zhuǎn)載請(qǐng)注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。
之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請(qǐng)求,又是如何生成響應(yīng)并最終呈現(xiàn)給用戶的工作原理。
本章將帶領(lǐng)大家研究另一個(gè) Laravel 框架的核心內(nèi)容:「服務(wù)容器」。有閱讀過 Laravel 文檔 的朋友應(yīng)該有注意到在「核心架構(gòu)」篇章中包含了幾個(gè)主題:生命周期、服務(wù)容器、服務(wù)提供者、Facades 和 Concracts.
今天就讓我們一起來揭開「Laravel 服務(wù)容器」的神秘面紗。
提示:本文內(nèi)容較長可能需要耗費(fèi)較多的閱讀時(shí)間,另外文中包含 Laravel 內(nèi)核代碼建議選擇合適的 IDE 或文本編輯器進(jìn)行源碼閱讀。目錄結(jié)構(gòu)
序章
依賴注入基本概念
什么是依賴注入
什么是依賴注入容器
什么是控制反轉(zhuǎn)(IoC)
Laravel 服務(wù)容器是什么
小結(jié)
Laravel 服務(wù)容器的使用方法
管理待創(chuàng)建類的依賴
常用綁定方法
bind 簡單綁定
singleton 單例綁定
instance 實(shí)例綁定
contextual-binding 上下文綁定
自動(dòng)注入和解析
Laravel 服務(wù)容器實(shí)現(xiàn)原理
注冊(cè)基礎(chǔ)服務(wù)
注冊(cè)基礎(chǔ)服務(wù)提供者
注冊(cè)核心服務(wù)別名到容器
管理所需創(chuàng)建的類及其依賴
bind 方法執(zhí)行原理
make 解析處理
資料
序章如果您有閱讀我的前作 深度挖掘 Laravel 生命周期 一文,你應(yīng)該已經(jīng)注意到「APP 容器」、「服務(wù)容器」、「綁定」和「解析」這些字眼。沒錯(cuò)這些技術(shù)都和「Laravel 服務(wù)容器」有著緊密的聯(lián)系。
在學(xué)習(xí)什么是「Laravel 服務(wù)容器」之前,如果您對(duì)「IoC(控制反轉(zhuǎn))」、「DI(依賴注入)」和「依賴注入容器」等相關(guān)知識(shí)還不夠了解的話,建議先學(xué)習(xí)一下這些資料:
Inversion of Control Containers and the Dependency Injection pattern:學(xué)習(xí)依賴注入必讀經(jīng)典;
依賴注入系列教程:原教程由 Symfony 框架的創(chuàng)造者所寫,我給出的是我翻譯的文章。原教程一共分 6 篇,前兩篇講解了依賴注入基礎(chǔ)知識(shí),后 4 篇講解依賴注入在 Symfony 中的應(yīng)用,所以可作為選讀材料;
深入淺出依賴注入:這是本人所寫的關(guān)于依賴注入的文章,試圖以一種易于理解的行文講解什么是「依賴注入」這種設(shè)計(jì)模式。
雖然,這些學(xué)習(xí)資料都有細(xì)致的講解容器相關(guān)的概念。但介紹一下與「Laravel 服務(wù)容器」有關(guān)的基本概念仍然有必要。
依賴注入基本概念這個(gè)小節(jié)會(huì)捎帶講解下「IoC(控制反轉(zhuǎn))」、「DI(依賴注入)」和「依賴注入容器」這些概念。
什么是依賴注入應(yīng)用程序?qū)π枰褂玫囊蕾嚒覆寮乖诰幾g(編碼)階段僅依賴于接口的定義,到運(yùn)行階段由一個(gè)獨(dú)立的組裝模塊(容器)完成對(duì)實(shí)現(xiàn)類的實(shí)例化工作,并將其「注射」到應(yīng)用程序中稱之為「依賴注入」。
一言以蔽之:面向接口編程。
至于如何實(shí)現(xiàn)面向接口編程,在 依賴注入系列教程 的前兩篇中有實(shí)例演示,感興趣的朋友可以去閱讀這個(gè)教程。更多細(xì)節(jié)可以閱讀 Inversion of Control Containers and the Dependency Injection pattern 和 深入淺出依賴注入。
什么是依賴注入容器在依賴注入過程中,由一個(gè)獨(dú)立的組裝模塊(容器)完成對(duì)實(shí)現(xiàn)類的實(shí)例化工作,那么這個(gè)組裝模塊就是「依賴注入容器」。
通俗一點(diǎn)講,使用「依賴注入容器」時(shí)無需人肉使用 new 關(guān)鍵字去實(shí)例化所依賴的「插件」,轉(zhuǎn)而由「依賴注入容器」自動(dòng)的完成一個(gè)模塊的組裝、配置、實(shí)例化等工作。
什么是控制反轉(zhuǎn)(IoC)IoC 是 Inversion of Control 的簡寫,通常被稱為控制反轉(zhuǎn),控制反轉(zhuǎn)從字面上來說比較不容易被理解。
要掌握什么是「控制反轉(zhuǎn)」需要整明白項(xiàng)目中「控制反轉(zhuǎn)」究竟「反轉(zhuǎn)」了哪方面的「控制」,它需要解決如何去定位(獲取)服務(wù)所需要的依賴的實(shí)現(xiàn)。
實(shí)現(xiàn)控制反轉(zhuǎn)時(shí),通過將原先在模塊內(nèi)部完成具體實(shí)現(xiàn)類的實(shí)例化,移至模塊的外部,然后再通過「依賴注入」的方式將具體實(shí)例「注入」到模塊內(nèi)即完成了對(duì)控制的反轉(zhuǎn)操作。
「依賴注入」的結(jié)果就是「控制反轉(zhuǎn)」的目的,也就說 控制反轉(zhuǎn) 的最終目標(biāo)是為了 實(shí)現(xiàn)項(xiàng)目的高內(nèi)聚低耦合,而 實(shí)現(xiàn)這種目標(biāo) 的方式則是通過 依賴注入 這種設(shè)計(jì)模式。
以上就是一些有關(guān)服務(wù)容器的一些基本概念。和我前面說的一樣,本文不是一篇講解依賴注入的文章,所以更多的細(xì)節(jié)需要大家自行去學(xué)習(xí)我之前列出的參考資料。
接下來才是今天的正餐,我將從以下幾個(gè)角度講解 Laravel 服務(wù)容器的相關(guān)內(nèi)容:
Laravel 服務(wù)容器是什么;
Laravel 服務(wù)容器的使用方法;
Laravel 服務(wù)容器技術(shù)原理。
Laravel 服務(wù)容器是什么在 Laravel 文檔 中,有一段關(guān)于 Laravel 服務(wù)容器的介紹:
Laravel 服務(wù)容器是用于管理類的依賴和執(zhí)行依賴注入的工具。依賴注入這個(gè)花俏名詞實(shí)質(zhì)上是指:類的依賴項(xiàng)通過構(gòu)造函數(shù),或者某些情況下通過「setter」方法「注入」到類中。
劃下重點(diǎn),「Laravel 服務(wù)容器」是用于 管理類的依賴 和 執(zhí)行依賴注入 的 工具。
通過前一節(jié)「依賴注入基本概念」相關(guān)闡述,我們不難得出這樣一個(gè)簡單的結(jié)論「Laravel 服務(wù)容器」就是「依賴注入容器」。
其實(shí),服務(wù)容器作為「依賴注入容器」去完成 Laravel 所需依賴的注冊(cè)、綁定和解析工作只是 「Laravel 服務(wù)容器」核心功能之一;另外,「Laravel 服務(wù)容器」還擔(dān)綱 Laravel 應(yīng)用的注冊(cè)程序的功能。
節(jié)選一段「深度挖掘 Laravel 生命周期」一文中有關(guān)服務(wù)容器的內(nèi)容:
創(chuàng)建應(yīng)用實(shí)例即實(shí)例化 IlluminateFoundationApplication 這個(gè)服務(wù)容器,后續(xù)我們稱其為 APP 容器。在創(chuàng)建 APP 容器主要會(huì)完成:注冊(cè)應(yīng)用的基礎(chǔ)路徑并將路徑綁定到 APP 容器 、注冊(cè)基礎(chǔ)服務(wù)提供者至 APP 容器 、注冊(cè)核心容器別名至 APP 容器 等基礎(chǔ)服務(wù)的注冊(cè)工作。
所以要了解 Larvel 服務(wù)容器必然需要研究 IlluminateFoundationApplication 的構(gòu)造函數(shù):
/** * Create a new Illuminate application instance. * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27 * @param string|null $basePath * @return void */ public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); }
沒錯(cuò)在 Application 類的構(gòu)造函數(shù)一共完成 3 個(gè)操作的處理功能:
通過 registerBaseBindings() 方法將「App 實(shí)例(即 Laravel 服務(wù)容器)」自身注冊(cè)到「Laravel 服務(wù)容器」;
通過 registerBaseServiceProviders() 注冊(cè)應(yīng)用 Laravel 框架的基礎(chǔ)服務(wù)提供者;
通過 registerCoreContainerAliases() 將具體的「依賴注入容器」及其別名注冊(cè)到「Laravel 服務(wù)容器」。
這里所說的「注冊(cè)」歸根到底還是在執(zhí)行「Laravel 服務(wù)容器」的「綁定(bind)」操作,完成綁定接口到實(shí)現(xiàn)。
為了表名我所言非虛,讓我們看看 registerBaseBindings() 方法:
/** * Register the basic bindings into the container. 注冊(cè) App 實(shí)例本身到 App 容器 * * @return void */ protected function registerBaseBindings() { static::setInstance($this); $this->instance("app", $this); $this->instance(Container::class, $this); $this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() )); }
我們知道 instance() 方法會(huì)將對(duì)象實(shí)例 $this** 綁定到容器的 **app** 和 **Container::class** 接口。后續(xù)無論是通過 **app()->make("app")** 還是 **app()->make(ontainer::class)** 獲取到的實(shí)現(xiàn)類都是 **$this(即 Laravel 服務(wù)容器實(shí)例) 對(duì)象。有關(guān) instance 的使用方法可以查閱 Laravel 服務(wù)容器解析文檔,不過我也會(huì)在下文中給出相關(guān)使用說明。
到這里相信大家對(duì)「Laravel 服務(wù)容器」有了一個(gè)比較清晰的理解了。
小結(jié)我們所說的「Laravel 服務(wù)容器」除了擔(dān)綱「依賴注入容器」職能外;同時(shí),還會(huì)作為 Laravel 項(xiàng)目的注冊(cè)中心去完成基礎(chǔ)服務(wù)的注冊(cè)工作。直白一點(diǎn)講在它的內(nèi)部會(huì)將諸多服務(wù)的實(shí)現(xiàn)類「綁定」到「Laravel 服務(wù)容器」。總結(jié)起來它的作用主要可以歸為以下 2 方面:
注冊(cè)基礎(chǔ)服務(wù);
管理所需創(chuàng)建的類及其依賴。
Laravel 服務(wù)容器的使用方法Laravel 服務(wù)容器在使用時(shí)一般分為兩個(gè)階段:使用之前進(jìn)行綁定(bind)完成將實(shí)現(xiàn)綁定到接口;使用時(shí)對(duì)通過接口解析(make)出服務(wù)。
Laravel 內(nèi)置多種不同的綁定方法以用于不同的使用場景。但無論哪種綁定方式,它們的最終目標(biāo)是一致的:綁定接口到實(shí)現(xiàn)。
這樣的好處是在項(xiàng)目的編碼階段建立起接口和實(shí)現(xiàn)的映射關(guān)系,到使用階段通過抽象類(接口)解析出它的具體實(shí)現(xiàn),這樣就實(shí)現(xiàn)了項(xiàng)目中的解耦。
在講解這些綁定方法前,先講一個(gè) Laravel 服務(wù)容器的使用場景。
管理待創(chuàng)建類的依賴通過向服務(wù)容器中綁定需要?jiǎng)?chuàng)建的類及其依賴,當(dāng)需要使用這個(gè)類時(shí)直接從服務(wù)容器中解析出這個(gè)類的實(shí)例。類的實(shí)例化及其依賴的注入,完全由服務(wù)容器自動(dòng)的去完成。
舉個(gè)示例,相比于通過 new 關(guān)鍵詞創(chuàng)建類實(shí)例:
每次實(shí)例化時(shí)我們都需要手動(dòng)的將依賴 $dependency 傳入到構(gòu)造函數(shù)內(nèi)。
而如果我們通過「Laravel 服務(wù)容器」綁定來管理依賴的話:
僅需在匿名函數(shù)內(nèi)一次創(chuàng)建所需依賴 $dependency,再將依賴傳入到服務(wù)進(jìn)行實(shí)例化,并返回服務(wù)實(shí)例。
此時(shí),使用 Cache 服務(wù)時(shí)只要從「Laravel 服務(wù)容器」中解析(make)出來即可,而無需每次手動(dòng)傳入 ConfigDependency 依賴再實(shí)例化服務(wù)。因?yàn)椋械囊蕾囎⑷牍ぷ鞔藭r(shí)都由 Laravel 服務(wù)容器 自動(dòng)的給我們做好了,這樣就簡化了服務(wù)處理。
下面演示了如何解析出 Cache 服務(wù):
先了解 Laravel 服務(wù)容器的一個(gè)使用場景,會(huì)對(duì)學(xué)習(xí)服務(wù)容器的 綁定方式 大有裨益。
從 Laravel 服務(wù)容器解析 - 綁定 這部分的文檔我們知道常用的綁定方式有:
bind($abstract, $concrete) 簡單綁定:將實(shí)現(xiàn)綁定到接口,解析時(shí)每次返回新的實(shí)例;
singleton($abstract, $concrete) 單例綁定:將實(shí)現(xiàn)綁定到接口,與 bind 方法不同的是首次解析是創(chuàng)建實(shí)例,后續(xù)解析時(shí)直接獲取首次解析的實(shí)例對(duì)象;
instance($abstract, $instance) 實(shí)例綁定:將實(shí)現(xiàn)實(shí)例綁定到接口;
上下文綁定和自動(dòng)注入。
接下來我們將學(xué)習(xí)這些綁定方法。
常用綁定方法 bind 簡單綁定bind 方法的功能是將服務(wù)的實(shí)現(xiàn)綁定到抽象類,然后在每次執(zhí)行服務(wù)解析操作時(shí),Laravel 容器都會(huì)重新創(chuàng)建實(shí)例對(duì)象。
bind 的使用方法已經(jīng)在「管理待創(chuàng)建類的依賴」一節(jié)中有過簡單的演示,它會(huì)在每次使用 App::make(Cache::class) 去解析 Cache 服務(wù)時(shí),重新執(zhí)行「綁定」操作中定義的閉包而重新創(chuàng)建 MemcachedCache 緩存實(shí)例。
bind 方法除了能夠接收閉包作為實(shí)現(xiàn)外,還可以:
接收具體實(shí)現(xiàn)類的類名;
接收 null 值以綁定自身。
singleton 單例綁定采用單例綁定時(shí),僅在首次解析時(shí)創(chuàng)建實(shí)例,后續(xù)使用 make 進(jìn)行解析服務(wù)操作都將直接獲取這個(gè)已解析的對(duì)象,實(shí)現(xiàn)了 共享 操作。
綁定處理類似 bind 綁定,只需將 bind 方法替換成 singleton 方法即可:
App::singleton(Cache::class, function () { $dependency = new ConfigDependency(config("cache.config.setting")); return $cache = new MemcachedCache($dependency); });instance 實(shí)例綁定實(shí)例綁定的功能是將已經(jīng)創(chuàng)建的實(shí)例對(duì)象綁定到接口以供后續(xù)使用,這種使用場景類似于 注冊(cè)表。
比如用于存儲(chǔ)用戶模型:
contextual-binding 上下文綁定在了解上下文綁定之前,先解釋下什么是上下文,引用「輪子哥」的一段解釋:
每一段程序都有很多外部變量。只有像Add這種簡單的函數(shù)才是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨(dú)立運(yùn)行。你為了使他們運(yùn)行,就要給所有的外部變量一個(gè)一個(gè)寫一些值進(jìn)去。這些值的集合就叫上下文。 「編程中什么是「Context(上下文)」?」 - vczh的回答。上下文綁定在 Laravel 服務(wù)容器解析 - 上下文綁定 文檔中給出了相關(guān)示例:
use IlluminateSupportFacadesStorage; use AppHttpControllersPhotoController; use AppHttpControllersVideoController; use IlluminateContractsFilesystemFilesystem; $this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk("local"); }); $this->app->when(VideoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk("s3"); });在項(xiàng)目中常會(huì)用到存儲(chǔ)功能,得益于 Laravel 內(nèi)置集成了 FlySystem 的 Filesystem 接口,我們很容易實(shí)現(xiàn)多種存儲(chǔ)服務(wù)的項(xiàng)目。
示例中將用戶頭像存儲(chǔ)到本地,將用戶上傳的小視頻存儲(chǔ)到云服務(wù)。那么這個(gè)時(shí)就需要區(qū)分這樣不同的使用場景(即上下文或者說環(huán)境)。
當(dāng)用戶存儲(chǔ)頭像(PhotoController::class)需要使用存儲(chǔ)服務(wù)(Filesystem::class)時(shí),我們將本地存儲(chǔ)驅(qū)動(dòng),作為實(shí)現(xiàn)給到 PhotoController::class:
function () { return Storage::disk("local"); }而當(dāng)用戶上傳視頻 VideoController::class,需要使用存儲(chǔ)服務(wù)(Filesystem::class)時(shí),我們則將云服務(wù)驅(qū)動(dòng),作為實(shí)現(xiàn)給到 VideoController::class:
function () { return Storage::disk("s3"); }這樣就實(shí)現(xiàn)了基于不同的環(huán)境獲取不同的服務(wù)實(shí)現(xiàn)。
自動(dòng)注入和解析「Laravel 服務(wù)容器」功能強(qiáng)大的原因在于除了提供手動(dòng)的綁定接口到實(shí)現(xiàn)的方法,還支持自動(dòng)注入和解析的功能。
我們?cè)诰帉懣刂破鲿r(shí),經(jīng)常會(huì)使用類型提示功能將某個(gè)類作為依賴傳入構(gòu)造函數(shù);但在執(zhí)行這個(gè)類時(shí)卻無需我們?nèi)?shí)例化這個(gè)類所需的依賴,這一切歸功于自動(dòng)解析的能力。
比如,我們的用戶控制器需要獲取用戶信息,然后在構(gòu)造函數(shù)中定義 User 模型作為依賴:
user = $user; } }然后,當(dāng)訪問用戶模塊時(shí) Laravel 會(huì)自動(dòng)解析出 User 模型,而無需手動(dòng)的常見模型示例。
除了以上幾種數(shù)據(jù)綁定方法外還有 tag(標(biāo)簽綁定) 和 extend(擴(kuò)展綁定) 等,毫無疑問這些內(nèi)容在 Laravel 文檔 也有介紹,所以這里就不再過多介紹了。
下一節(jié),我們將深入到源碼中去窺探下 Laravel 服務(wù)容器是如何進(jìn)行綁定和解析處理的。
Laravel 服務(wù)容器實(shí)現(xiàn)原理要了解一項(xiàng)技術(shù)的實(shí)現(xiàn)原理,免不了去探索源碼,源碼學(xué)習(xí)是個(gè)有意思的事情。這個(gè)過程不但讓我們理解它是如何工作的,或許還會(huì)帶給我們一些意外驚喜。
我們知道 Laravel 服務(wù)容器其實(shí)會(huì)處理以下兩方面的工作:
注冊(cè)基礎(chǔ)服務(wù);
管理所需創(chuàng)建的類及其依賴。
注冊(cè)基礎(chǔ)服務(wù)關(guān)于注冊(cè)基礎(chǔ)服務(wù),在「深度挖掘 Laravel 生命周期」一文中其實(shí)已經(jīng)有所涉及,但并并不深入。
本文將進(jìn)一步的研究注冊(cè)基礎(chǔ)服務(wù)的細(xì)節(jié)。除了研究這些服務(wù)究竟如何被注冊(cè)到服務(wù)容器,還將學(xué)習(xí)它們是如何被使用的。所有的這些都需要我們深入到 IlluminateFoundationApplication 類的內(nèi)部:
/** * Create a new Illuminate application instance. * * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27 * @param string|null $basePath * @return void */ public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); }前面我們已經(jīng)研究過 registerBaseBindings() 方法,了解到該方法主要是將自身綁定到了服務(wù)容器,如此我們便可以在項(xiàng)目中使用 $this->app->make("something") 去解析一項(xiàng)服務(wù)。
現(xiàn)在讓我們將焦點(diǎn)集中到 registerBaseServiceProviders 和 registerCoreContainerAliases 這兩個(gè)方法。
注冊(cè)基礎(chǔ)服務(wù)提供者打開 registerBaseServiceProviders 方法將發(fā)現(xiàn)在方法體中僅有 3 行代碼,分別是注冊(cè) EventServiceProvider、LogServiceProvider 和 RoutingServiceProvider 這 3 個(gè)服務(wù)提供者:
/** * 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)); } /** * Register a service provider with the application. * * @param IlluminateSupportServiceProvider|string $provider * @param array $options * @param bool $force * @return IlluminateSupportServiceProvider */ public function register($provider, $options = [], $force = false) { if (($registered = $this->getProvider($provider)) && ! $force) { return $registered; } // If the given "provider" is a string, we will resolve it, passing in the // application instance automatically for the developer. This is simply // a more convenient way of specifying your service provider classes. if (is_string($provider)) { $provider = $this->resolveProvider($provider); } // 當(dāng)服務(wù)提供者存在 register 方法時(shí),執(zhí)行 register 方法,完成綁定處理 if (method_exists($provider, "register")) { $provider->register(); } $this->markAsRegistered($provider); // If the application has already booted, we will call this boot method on // the provider class so it has an opportunity to do its boot logic and // will be ready for any usage by this developer"s application logic. // 執(zhí)行服務(wù)提供者 boot 方法啟動(dòng)程序 if ($this->booted) { $this->bootProvider($provider); } return $provider; } /** * Boot the given service provider. 啟動(dòng)給定服務(wù)提供者 * * @param IlluminateSupportServiceProvider $provider * @return mixed */ protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, "boot")) { return $this->call([$provider, "boot"]); } }Laravel 服務(wù)容器在執(zhí)行注冊(cè)方法時(shí),需要進(jìn)行如下處理:
如果服務(wù)提供者存在 register 方法,會(huì)將服務(wù)實(shí)現(xiàn)綁定到容器操作 $provider->register();;
如果服務(wù)提供者存在 boot 方法,會(huì)在 bootProvider 方法內(nèi)執(zhí)行啟動(dòng)方法來啟動(dòng)這個(gè)服務(wù)。
值得指出的是在服務(wù)提供者的 register 方法中,最好僅執(zhí)行「綁定」操作。
為了更好的說明服務(wù)提供者僅完成綁定操作,還是讓我們來瞧瞧 EventServiceProvider 服務(wù),看看它究竟做了什么:
app->singleton("events", function ($app) { return (new Dispatcher($app))->setQueueResolver(function () use ($app) { return $app->make(QueueFactoryContract::class); }); }); } }沒錯(cuò) EventServiceProvider 所做的全部事情,僅僅通過 register 方法將閉包綁定到了服務(wù)容器,除此之外就什么都沒有了。
注冊(cè)核心服務(wù)別名到容器用過 Laravel 框架的朋友應(yīng)該知道在 Laravel 中有個(gè)別名系統(tǒng)。最常見的使用場景就是設(shè)置路由時(shí),可以通過 Route 類完成一個(gè)新路由的注冊(cè),如:
Route::get("/", function() { return "Hello World"; });得益于 Laravel Facades 和別名系統(tǒng)我們可以很方便的通過別名來使用 Laravel 內(nèi)置提供的各種服務(wù)。
注冊(cè)別名和對(duì)應(yīng)服務(wù)的映射關(guān)系,便是在 registerCoreContainerAliases 方法內(nèi)來完成的。由于篇幅所限本文就不做具體細(xì)節(jié)的展開,后續(xù)會(huì)多帶帶出一篇講解別名系統(tǒng)的文章。
不過現(xiàn)在還是有必要瀏覽下 Laravel 提供了哪些別名服務(wù):
/** * Register the core class aliases in the container. 在容器中注冊(cè)核心服務(wù)的別名 * * @return void */ public function registerCoreContainerAliases() { foreach ([ "app" => [IlluminateFoundationApplication::class, IlluminateContractsContainerContainer::class, IlluminateContractsFoundationApplication::class, PsrContainerContainerInterface::class], "auth" => [IlluminateAuthAuthManager::class, IlluminateContractsAuthFactory::class], "auth.driver" => [IlluminateContractsAuthGuard::class], "blade.compiler" => [IlluminateViewCompilersBladeCompiler::class], "cache" => [IlluminateCacheCacheManager::class, IlluminateContractsCacheFactory::class], "cache.store" => [IlluminateCacheRepository::class, IlluminateContractsCacheRepository::class], "config" => [IlluminateConfigRepository::class, IlluminateContractsConfigRepository::class], "cookie" => [IlluminateCookieCookieJar::class, IlluminateContractsCookieFactory::class, IlluminateContractsCookieQueueingFactory::class], "encrypter" => [IlluminateEncryptionEncrypter::class, IlluminateContractsEncryptionEncrypter::class], "db" => [IlluminateDatabaseDatabaseManager::class], "db.connection" => [IlluminateDatabaseConnection::class, IlluminateDatabaseConnectionInterface::class], "events" => [IlluminateEventsDispatcher::class, IlluminateContractsEventsDispatcher::class], "files" => [IlluminateFilesystemFilesystem::class], "filesystem" => [IlluminateFilesystemFilesystemManager::class, IlluminateContractsFilesystemFactory::class], "filesystem.disk" => [IlluminateContractsFilesystemFilesystem::class], "filesystem.cloud" => [IlluminateContractsFilesystemCloud::class], "hash" => [IlluminateContractsHashingHasher::class], "translator" => [IlluminateTranslationTranslator::class, IlluminateContractsTranslationTranslator::class], "log" => [IlluminateLogWriter::class, IlluminateContractsLoggingLog::class, PsrLogLoggerInterface::class], "mailer" => [IlluminateMailMailer::class, IlluminateContractsMailMailer::class, IlluminateContractsMailMailQueue::class], "auth.password" => [IlluminateAuthPasswordsPasswordBrokerManager::class, IlluminateContractsAuthPasswordBrokerFactory::class], "auth.password.broker" => [IlluminateAuthPasswordsPasswordBroker::class, IlluminateContractsAuthPasswordBroker::class], "queue" => [IlluminateQueueQueueManager::class, IlluminateContractsQueueFactory::class, IlluminateContractsQueueMonitor::class], "queue.connection" => [IlluminateContractsQueueQueue::class], "queue.failer" => [IlluminateQueueFailedFailedJobProviderInterface::class], "redirect" => [IlluminateRoutingRedirector::class], "redis" => [IlluminateRedisRedisManager::class, IlluminateContractsRedisFactory::class], "request" => [IlluminateHttpRequest::class, SymfonyComponentHttpFoundationRequest::class], "router" => [IlluminateRoutingRouter::class, IlluminateContractsRoutingRegistrar::class, IlluminateContractsRoutingBindingRegistrar::class], "session" => [IlluminateSessionSessionManager::class], "session.store" => [IlluminateSessionStore::class, IlluminateContractsSessionSession::class], "url" => [IlluminateRoutingUrlGenerator::class, IlluminateContractsRoutingUrlGenerator::class], "validator" => [IlluminateValidationFactory::class, IlluminateContractsValidationFactory::class], "view" => [IlluminateViewFactory::class, IlluminateContractsViewFactory::class], ] as $key => $aliases) { foreach ($aliases as $alias) { $this->alias($key, $alias); } } }管理所需創(chuàng)建的類及其依賴對(duì)于 Laravel 服務(wù)容器來講,其內(nèi)部實(shí)現(xiàn)上無論是 bind、singleton、tag 還是 extend 它們的基本原理大致類似。所以本文中我們僅研究 bind 綁定來管中窺豹。
我們知道綁定方法定義在 Laravel 服務(wù)容器 IlluminateFoundationApplication 類內(nèi),而 Application繼承自 IlluminateContainerContainer 類。這些與服務(wù)容器綁定相關(guān)的方法便直接繼承自 Container 類。
bind 方法執(zhí)行原理bind 綁定作為最基本的綁定方法,可以很好的說明 Laravel 是如何實(shí)現(xiàn)綁定服務(wù)處理的。
下面摘出 Container 容器中 bind 方法及其相關(guān)聯(lián)的方法。由于綁定處理中涉及較多方法,所以我直接將重要的代碼片段相關(guān)注釋做了翻譯及補(bǔ)充說明,以便閱讀:
/** * Register a binding with the container. * * @param string $abstract * @param Closure|string|null $concrete * @param bool $shared * @return void */ public function bind($abstract, $concrete = null, $shared = false) { // 如果未提供實(shí)現(xiàn)類 $concrete,我們直接將抽象類作為實(shí)現(xiàn) $abstract。 // 這之后,我們無需明確指定 $abstract 和 $concrete 是否為單例模式, // 而是通過 $shared 標(biāo)識(shí)來決定它們是單例還是每次都需要實(shí)例化處理。 $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } // 如果綁定時(shí)傳入的實(shí)現(xiàn)類非閉包,即綁定時(shí)是直接給定了實(shí)現(xiàn)類的類名, // 這時(shí)要稍微處理下將類名封裝成一個(gè)閉包,保證解析時(shí)處理手法的統(tǒng)一。 if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact("concrete", "shared"); // 最后如果抽象類已經(jīng)被容器解析過,我們將觸發(fā) rebound 監(jiān)聽器。 // 并且通過觸發(fā) rebound 監(jiān)聽器回調(diào),將任何已被解析過的服務(wù)更新最新的實(shí)現(xiàn)到抽象接口。 if ($this->resolved($abstract)) { $this->rebound($abstract); } } /** * Get the Closure to be used when building a type. 當(dāng)綁定實(shí)現(xiàn)為類名時(shí),則封裝成閉包并返回。 * * @param string $abstract * @param string $concrete * @return Closure */ protected function getClosure($abstract, $concrete) { return function ($container, $parameters = []) use ($abstract, $concrete) { if ($abstract == $concrete) { return $container->build($concrete); } return $container->make($concrete, $parameters); }; } /** * Fire the "rebound" callbacks for the given abstract type. 依據(jù)給定的抽象服務(wù)接口,觸發(fā)其 "rebound" 回調(diào) * * @param string $abstract * @return void */ protected function rebound($abstract) { $instance = $this->make($abstract); foreach ($this->getReboundCallbacks($abstract) as $callback) { call_user_func($callback, $this, $instance); } } /** * Get the rebound callbacks for a given type. 獲取給定抽象服務(wù)的回調(diào)函數(shù)。 * * @param string $abstract * @return array */ protected function getReboundCallbacks($abstract) { if (isset($this->reboundCallbacks[$abstract])) { return $this->reboundCallbacks[$abstract]; } return []; }在 bind 方法中,主要完成以下幾個(gè)方面的處理:
干掉之前解析過的服務(wù)實(shí)例;
將綁定的實(shí)現(xiàn)類封裝成閉包,以確保后續(xù)處理的統(tǒng)一;
針對(duì)已解析過的服務(wù)實(shí)例,再次觸發(fā)重新綁定回調(diào)函數(shù),同時(shí)將最新的實(shí)現(xiàn)類更新到接口里面。
在綁定過程中,服務(wù)容器并不會(huì)執(zhí)行服務(wù)的解析操作,這樣有利于提升服務(wù)的性能。直到在項(xiàng)目運(yùn)行期間,被使用時(shí)才會(huì)真正解析出需要使用的對(duì)應(yīng)服務(wù),實(shí)現(xiàn)「按需加載」。
make 解析處理解析處理和綁定一樣定義在 IlluminateContainerContainer 類中,無論是手動(dòng)解析還是通過自動(dòng)注入的方式,實(shí)現(xiàn)原理都是基于 PHP 的反射機(jī)制。
所有我們還是直接從 make 方法開始去挖出相關(guān)細(xì)節(jié):
/** * Resolve the given type from the container. 從容器中解析出給定服務(wù)具體實(shí)現(xiàn) * * @param string $abstract * @param array $parameters * @return mixed */ public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); } /** * Resolve the given type from the container. 從容器中解析出給定服務(wù)具體實(shí)現(xiàn) * * @param string $abstract * @param array $parameters * @return mixed */ protected function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); // 如果綁定時(shí)基于上下文綁定,此時(shí)需要解析出上下文實(shí)現(xiàn)類 $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); // 如果給定的類型已單例模式綁定,直接從服務(wù)容器中返回這個(gè)實(shí)例而無需重新實(shí)例化 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); // 已準(zhǔn)備就緒創(chuàng)建這個(gè)綁定的實(shí)例。下面將實(shí)例化給定實(shí)例及內(nèi)嵌的所有依賴實(shí)例。 // 到這里我們已經(jīng)做好創(chuàng)建實(shí)例的準(zhǔn)備工作。只有可以構(gòu)建的服務(wù)才可以執(zhí)行 build 方法去實(shí)例化服務(wù); // 否則也就是說我們的服務(wù)還存在依賴,然后不斷的去解析嵌套的依賴,知道它們可以去構(gòu)建(isBuildable)。 if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } // 如果我們的服務(wù)存在擴(kuò)展(extend)綁定,此時(shí)就需要去執(zhí)行擴(kuò)展。 // 擴(kuò)展綁定適用于修改服務(wù)的配置或者修飾(decorating)服務(wù)實(shí)現(xiàn)。 foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // 如果我們的服務(wù)已單例模式綁定,此時(shí)無要將已解析的服務(wù)緩存到單例對(duì)象池中(instances), // 后續(xù)便可以直接獲取單例服務(wù)對(duì)象了。 if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); $this->resolved[$abstract] = true; array_pop($this->with); return $object; } /** * Determine if the given concrete is buildable. 判斷給定的實(shí)現(xiàn)是否立馬進(jìn)行構(gòu)建 * * @param mixed $concrete * @param string $abstract * @return bool */ protected function isBuildable($concrete, $abstract) { // 僅當(dāng)實(shí)現(xiàn)類和接口相同或者實(shí)現(xiàn)為閉包時(shí)可構(gòu)建 return $concrete === $abstract || $concrete instanceof Closure; } /** * Instantiate a concrete instance of the given type. 構(gòu)建(實(shí)例化)給定類型的實(shí)現(xiàn)類(匿名函數(shù))實(shí)例 * * @param string $concrete * @return mixed * * @throws IlluminateContractsContainerBindingResolutionException */ public function build($concrete) { // 如果給定的實(shí)現(xiàn)是一個(gè)閉包,直接執(zhí)行并閉包,返回執(zhí)行結(jié)果 if ($concrete instanceof Closure) { return $concrete($this, $this->getLastParameterOverride()); } $reflector = new ReflectionClass($concrete); // 如果需要解析的類無法實(shí)例化,即試圖解析一個(gè)抽象類類型如: 接口或抽象類而非實(shí)現(xiàn)類,直接拋出異常。 if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } $this->buildStack[] = $concrete; // 通過反射獲取實(shí)現(xiàn)類構(gòu)造函數(shù) $constructor = $reflector->getConstructor(); // 如果實(shí)現(xiàn)類并沒有定義構(gòu)造函數(shù),說明這個(gè)實(shí)現(xiàn)類沒有相關(guān)依賴。 // 我們可以直接實(shí)例化這個(gè)實(shí)現(xiàn)類,而無需自動(dòng)解析依賴(自動(dòng)注入)。 if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } // 獲取到實(shí)現(xiàn)類構(gòu)造函數(shù)依賴參數(shù) $dependencies = $constructor->getParameters(); // 解析出所有依賴 $instances = $this->resolveDependencies( $dependencies ); array_pop($this->buildStack); // 這是我們就可以創(chuàng)建服務(wù)實(shí)例并返回。 return $reflector->newInstanceArgs($instances); } /** * Resolve all of the dependencies from the ReflectionParameters. 從 ReflectionParameters 解析出所有構(gòu)造函數(shù)所需依賴 * * @param array $dependencies * @return array */ protected function resolveDependencies(array $dependencies) { $results = []; foreach ($dependencies as $dependency) { // If this dependency has a override for this particular build we will use // that instead as the value. Otherwise, we will continue with this run // of resolutions and let reflection attempt to determine the result. if ($this->hasParameterOverride($dependency)) { $results[] = $this->getParameterOverride($dependency); continue; } // 構(gòu)造函數(shù)參數(shù)為非類時(shí),即參數(shù)為 string、int 等標(biāo)量類型或閉包時(shí),按照標(biāo)量和閉包解析; // 否則需要解析類。 $results[] = is_null($dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } return $results; } /** * Resolve a non-class hinted primitive dependency. 依據(jù)類型提示解析出標(biāo)量類型(閉包)數(shù)據(jù) * * @param ReflectionParameter $parameter * @return mixed * * @throws IlluminateContractsContainerBindingResolutionException */ protected function resolvePrimitive(ReflectionParameter $parameter) { if (! is_null($concrete = $this->getContextualConcrete("$".$parameter->name))) { return $concrete instanceof Closure ? $concrete($this) : $concrete; } if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } $this->unresolvablePrimitive($parameter); } /** * Resolve a class based dependency from the container. 從服務(wù)容器中解析出類依賴(自動(dòng)注入) * * @param ReflectionParameter $parameter * @return mixed * * @throws IlluminateContractsContainerBindingResolutionException */ protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }以上,便是 Laravel 服務(wù)容器解析的核心,得益于 PHP 的反射機(jī)制,實(shí)現(xiàn)了自動(dòng)依賴注入和服務(wù)解析處理,概括起來包含以下步驟:
對(duì)于單例綁定數(shù)據(jù)如果一解析過服務(wù)則直接返回,否則繼續(xù)執(zhí)行解析;
非單例綁定的服務(wù)類型,通過接口獲取綁定實(shí)現(xiàn)類;
接口即服務(wù)或者閉包時(shí)進(jìn)行構(gòu)建(build)處理,構(gòu)建時(shí)依托于 PHP 反射機(jī)制進(jìn)行自動(dòng)依賴注入解析出完整的服務(wù)實(shí)例對(duì)象;否則繼續(xù)解析(make)出所有嵌套的依賴;
如果服務(wù)存在擴(kuò)展綁定,解析出擴(kuò)展綁定結(jié)果;
如果綁定服務(wù)為單例綁定類型(singleton),將解析到的服務(wù)加入到單例對(duì)象池;
其它處理如觸發(fā)綁定監(jiān)聽器、將服務(wù)標(biāo)記為已解析狀態(tài)等,并返回服務(wù)實(shí)例。
更多細(xì)節(jié)處理還是需要我們進(jìn)一步深入的內(nèi)核中才能發(fā)掘出來,但到這其實(shí)已經(jīng)差不太多了。有興趣的朋友可以親自了解下其它綁定方法的源碼解析處理。
以上便是今天 Laravel 服務(wù)容器的全部內(nèi)容,希望對(duì)大家有所啟發(fā)。
資料感謝一下優(yōu)秀的學(xué)習(xí)資料:
https://www.insp.top/learn-la...
https://laravel-china.org/art...
https://laravel-china.org/art...
https://hk.saowen.com/a/6c880...
http://rrylee.github.io/2015/...
https://blog.tanteng.me/2016/...
https://juejin.im/entry/5916a...
https://laravel-china.org/top...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/28702.html
摘要:服務(wù)提供者啟動(dòng)原理之前我們有學(xué)習(xí)深度挖掘生命周期和深入剖析服務(wù)容器,今天我們將學(xué)習(xí)服務(wù)提供者。的所有核心服務(wù)都是通過服務(wù)提供者進(jìn)行引導(dǎo)啟動(dòng)的,所以想深入了解那么研究服務(wù)提供者的原理是個(gè)繞不開的話題。 本文首發(fā)于 深入剖析 Laravel 服務(wù)提供者實(shí)現(xiàn)原理,轉(zhuǎn)載請(qǐng)注明出處。 今天我們將學(xué)習(xí) Laravel 框架另外一個(gè)核心內(nèi)容「服務(wù)提供者(Service Provider)」。服務(wù)提供...
摘要:外觀模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。將使用者與子系統(tǒng)從直接耦合,轉(zhuǎn)變成由外觀類提供統(tǒng)一的接口給使用者使用,以降低客戶端與子系統(tǒng)之間的耦合度。接下來將深入分析外觀服務(wù)的加載過程。引導(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...
摘要:一旦這一切完成,方法會(huì)運(yùn)行在類屬性在命令構(gòu)造后設(shè)置容器解析實(shí)例,在中我們?cè)O(shè)置了將使用的緩存驅(qū)動(dòng),我們也根據(jù)命令來決定我們調(diào)用什么方法。作業(yè)只在以上起效在上也無效處理作業(yè)方法調(diào)用觸發(fā)事件觸發(fā)事件。 譯文GitHub https://github.com/yuansir/diving-laravel-zh 原文鏈接https://divinglaravel.com/queue-system...
摘要:有幾種有用的方法可以使用將作業(yè)推送到特定的隊(duì)列在給定的秒數(shù)之后推送作業(yè)延遲后將作業(yè)推送到特定的隊(duì)列推送多個(gè)作業(yè)推送特定隊(duì)列上的多個(gè)作業(yè)調(diào)用這些方法之后,所選擇的隊(duì)列驅(qū)動(dòng)會(huì)將給定的信息存儲(chǔ)在存儲(chǔ)空間中,供按需獲取。 原文鏈接https://divinglaravel.com/queue-system/pushing-jobs-to-queue There are several ways...
1. 預(yù)備知識(shí) 1.1 composer 基本用法 1.1.1 參考文章 composer 基本用法 1.1.2 要求掌握的知識(shí)點(diǎn) composer 依賴管理 composer 自動(dòng)加載(關(guān)鍵) 1.2 DIP、IOC、DI、IOC 容器 詳情文章 2. Laravel 運(yùn)行機(jī)制剖析 2.1 場景 范例:http://laravel.com/test?name=chenxuelong 2.2 ...
閱讀 3661·2021-09-07 09:59
閱讀 720·2019-08-29 15:12
閱讀 803·2019-08-29 11:14
閱讀 1307·2019-08-26 13:27
閱讀 2659·2019-08-26 10:38
閱讀 3133·2019-08-23 18:07
閱讀 1271·2019-08-23 14:40
閱讀 1922·2019-08-23 12:38