框架啟動與服務(wù)容器源碼了解了服務(wù)容器的原理,要處理的問題,以及 Laravel 中如何使用服務(wù)容器以及服務(wù)提供者之后,我們就進(jìn)入到了源碼的學(xué)習(xí)中。其實(shí)服務(wù)容器的源碼還是比較好理解的,畢竟我們已經(jīng)自己實(shí)現(xiàn)過一個簡單的服務(wù)容器了。在這里,我們也順便看一下 Laravel 框架啟動時的容器加載情況。 框架啟動通過之前的學(xué)習(xí),我們已經(jīng)了解到 Laravel 是單一入口文件的框架。所以我們直接去 public/index.php 查看這個入口文件。 天啊,這也太明顯了吧,上來就加載了一個 bootstrap/app.php 這個文件,然后就開始使用 $app->make() 來調(diào)用容器的實(shí)現(xiàn)方法了。那么我們很清楚地就可以發(fā)現(xiàn),這個 bootstrap/app.php 就是一個服務(wù)容器。話不多說,馬上進(jìn)入到 bootstrap/app.php 文件中。 我們首先實(shí)例化了一個 Illuminate\Foundation\Application 對象,然后再實(shí)例化了幾個單例服務(wù),分別是 Http 的核心 Kernel 和命令行 Console 的核心 Kernel 對象,另外還有一個異??刂茖ο蟆5竭@里,你也一定會想到了,這個 Illuminate\Foundation\Application 就是我們整個 Laravel 框架的核心,也就是服務(wù)容器實(shí)現(xiàn)的核心。 Container 服務(wù)容器打開 laravel/framework/src/Illuminate/Foundation/Application.php 文件,我們可以看到這個類繼承的是一個叫做 Container 的類,這個單詞就是容器的意思。從這里我們就可以看出,Laravel 是以 Application 也就是應(yīng)用的意思來代替容器,但其實(shí)這個應(yīng)用就是一個容器。由此可見,本身整個運(yùn)行起來的 Laravel 就是一個超大的 Application 應(yīng)用。 bind在 Application 中,我們可以看到熟悉的 make() 和 boot() 方法,而 bind()、instance()、singleton() 方法則都在它的父類 Container 中實(shí)現(xiàn)的,我們先來看看 bind() 方法。 首先 dropStaleInstances() 是如果已經(jīng)有同名的容器實(shí)現(xiàn),也就是 instaces 數(shù)組中有的話,清理掉它,然后看實(shí)現(xiàn)參數(shù) concrete 是否為空,如果為空的話把容器名稱賦值給實(shí)現(xiàn)。接下來,判斷實(shí)現(xiàn)是否是匿名函數(shù)形式的,如果不是的話,轉(zhuǎn)換成一個匿名函數(shù)形式的實(shí)現(xiàn)方法。然后通過 compact() 函數(shù)將參數(shù)轉(zhuǎn)換成數(shù)據(jù)并保存在 bindings 數(shù)組中。 想必這兩個 instances 和 bindings 是干什么的不用我再多解釋了吧。最后的 resolved() 方法是判斷這個服務(wù)是否在默認(rèn)的別名應(yīng)用中,是否已經(jīng)有 resolved 解決方案實(shí)例,如果有的話,調(diào)用 rebound() 對象 make() 它出來。 很明顯,框架的代碼比我們實(shí)現(xiàn)的服務(wù)容器代碼可復(fù)雜多了,但是大體思想是一致的。至于后面的一些比較詭異的 resolved() 和 rebound() 是干嘛用的,我們后面再說。 singleton() 方法的實(shí)現(xiàn)就是調(diào)用的 bind() 方法,只不過最后一個 $shared 參數(shù)默認(rèn)給了一個 ture 。從名字可以看出,這個 shared 是共享的意思,而 singleton 是單例的意思,暫時我們推測,在 make() 的時候,我們會根據(jù)這個變量來確定要實(shí)現(xiàn)加載的這個對象是不是使用單例模式。答案我們在看 make() 方法的時候再研究。 instancesingleton() 方法直接調(diào)用的就是 bind() 方法,只是最后一個參數(shù)默認(rèn)給了一個 true ,所以我們也就不多說了,主要來看一下另外一個 instance() 方法。其實(shí)從上面代碼就可以看出,bind() 方法的第二個參數(shù)只能是 Closure 或者 string 以及 null 類型的。如果我們想直接綁定一個實(shí)例,就需要使用 instance() 方法。 在之前我們自己實(shí)現(xiàn)的那個容器類中,在 bind() 方法中直接進(jìn)行了判斷,如果是實(shí)例則直接放到 instances 數(shù)組中,而在 Laravel 中,則是分開了,必須在 instance() 方法中才會將實(shí)例保存到 instances 數(shù)組。 make最后我們再來看一下 make() 方法,也就是從服務(wù)容器中獲得我們的需要的對象。 make() 方法實(shí)際上調(diào)用的是 resolve() 這個方法,在這個方法內(nèi)部,我們可以看到最后直接返回的就是一個 $object 變量,很明顯,它將會是一個對象。這個 $object 是通過前面的一系列判斷并調(diào)用相應(yīng)的方法來獲得的,通過 getAlias() 我們會獲得需要實(shí)例化的對象是否有別名設(shè)置,這個設(shè)置主要是框架內(nèi)部的很多對象都會進(jìn)行一個別名配置,通常是框架比較核心的一些組件,然后 getContextualConcrete() 我們會獲得當(dāng)前容器中綁定的對象信息,接下來在 isBuildable() 中,判斷容器名是否和我們傳遞過來的名稱相同,以及容器內(nèi)容是否是一個回調(diào)函數(shù)。如果兩者有其一符合條件就進(jìn)入 build() 方法,如果都不符合使用查找到的容器名兩次調(diào)用 make() 方法。從這里我們會發(fā)現(xiàn),服務(wù)實(shí)例化的核心轉(zhuǎn)移到了 build() 方法中。 在 build() 方法中,先判斷綁定的容器內(nèi)容是不是一個回調(diào)函數(shù),如果是的話,直接調(diào)用這個回調(diào)函數(shù)并且返回了。如果不是回調(diào)函數(shù)的話,下面的內(nèi)容相信大家也不會陌生了,通過 反射 的方式來創(chuàng)建對象。高大上不,如果你在 bind() 方法中,使用的是一個 \App\ContainerTest\iPhone12::class ,這樣的類字符串,那么它就會通過反射來生成這個對應(yīng)的對象。 resolveDependencies() 用來解決類實(shí)例化時構(gòu)造函數(shù)的依賴問題,需要的參數(shù)也是通過上面反射時 getParameters() 方法獲取的。 ServiceProvider 服務(wù)提供者通過上面的幾個方法學(xué)習(xí),我們了解到了整個 Laravel 容器中最重要的幾個方法,也就是綁定實(shí)現(xiàn)以及獲得具體的實(shí)例對象,是不是和我們自己實(shí)現(xiàn)的那個服務(wù)容器非常像。當(dāng)然,就像之前我們說過的,在框架中的實(shí)現(xiàn)會比我們自己的實(shí)現(xiàn)要復(fù)雜很多。接下來我們看看服務(wù)提供者是怎么加載的。 回到 public/index.php 中,我們可以看到一段代碼。 這里調(diào)用了 kernel 的 handle() 方法,進(jìn)入 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 的 handle() 方法之后繼續(xù)查看 sendRequestThroughRouter() 方法,在這個方法中調(diào)用了一個 bootstrap() 方法。 $this->bootstrappers() 返回的就是在 Kernel 中的那個 bootstrappers 屬性,然后通過 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中的 bootstrapWith() 方法來加載這些預(yù)定義的服務(wù)提供者。 不對呀,這里都是預(yù)定義的服務(wù)提供者,我們自定義的那些服務(wù)提供者是在哪里加載的呢? 注意看 RegisterProviders ,bootstrapWith() 方法會直接調(diào)用這些預(yù)定義服務(wù)提供者的 bootstrap() 方法,而 RegisterProviders 中的 bootstrap() 方法只有一行代碼。 它又回來繼續(xù)調(diào)用 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中的 registerConfiguredProviders() 方法。 其實(shí)到這里就已經(jīng)很明顯了,我們看到了 $this->config['app.providers'] 這個變量,它就是獲得的 config/app.php 中的 providers 里面的內(nèi)容,然后通過后面的代碼將這些服務(wù)提供者注冊到服務(wù)容器中。 當(dāng)所有定義好的服務(wù)提供者注冊完成后,會繼續(xù)進(jìn)行 $bootstrappers 中 BootProviders 服務(wù)提供者的注冊,它會調(diào)用每個服務(wù)提供者的 boot() 方法完成各個服務(wù)的啟動加載。這一下,你就知道為什么 boot() 方法可以調(diào)用到所有的服務(wù)了吧。 框架核心通過來回查看 Kernel 和 Application ,相信你已經(jīng)明白整個框架的核心就是在這兩個類之間來回倒騰。默認(rèn)的服務(wù)實(shí)例以及服務(wù)提供者都在 Application 的構(gòu)造函數(shù)中進(jìn)行了預(yù)加載,比如說路由、門面等等。而我們自定義的那些服務(wù)提供者則是通過 RegisterProviders 并進(jìn)行配置讀取后也完成了加載。 除些之外 Application 的 registerCoreContainerAliases() 中做好了許多別名對象的服務(wù)配置,當(dāng)你搞不清楚為什么 $this->make('app') 可以使用的時候,就可以到這里來看一看。這些別名實(shí)例的定義最大的用途其實(shí)是在 門面 中使用,這個我們后面在講到門面的時候還會再說。 總結(jié)其實(shí)關(guān)于服務(wù)容器還有很多值得我們深入學(xué)習(xí)和挖掘的內(nèi)容,但限于篇幅和本人的水平有限,這里只是梳理了一個大概的流程。大家可以繼續(xù)順著這兩個核心的類,也就是 Kernel 和 Application 繼續(xù)研究和探索,相信你的收獲一定會更多。 |
|
|