电竞比分网-中国电竞赛事及体育赛事平台

分享

【Laravel系列6.3】框架啟動與服務(wù)容器源碼

 硬核項目經(jīng)理 2022-05-16 發(fā)布于湖南

框架啟動與服務(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 查看這個入口文件。

// public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Kernel::class);

$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

$kernel->terminate($request, $response);

天啊,這也太明顯了吧,上來就加載了一個 bootstrap/app.php 這個文件,然后就開始使用 $app->make() 來調(diào)用容器的實(shí)現(xiàn)方法了。那么我們很清楚地就可以發(fā)現(xiàn),這個 bootstrap/app.php 就是一個服務(wù)容器。話不多說,馬上進(jìn)入到 bootstrap/app.php 文件中。

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

我們首先實(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() 方法。

public function bind($abstract, $concrete = null, $shared = false)
{
    $this->dropStaleInstances($abstract);

    if (is_null($concrete)) {
        $concrete = $abstract;
    }

    if (! $concrete instanceof Closure) {
        if (! is_string($concrete)) {
            throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
        }

        $concrete = $this->getClosure($abstract, $concrete);
    }

    $this->bindings[$abstract] = compact('concrete''shared');

    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}

首先 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() 方法的時候再研究。

instance

singleton() 方法直接調(diào)用的就是 bind() 方法,只是最后一個參數(shù)默認(rèn)給了一個 true ,所以我們也就不多說了,主要來看一下另外一個 instance() 方法。其實(shí)從上面代碼就可以看出,bind() 方法的第二個參數(shù)只能是 Closure 或者 string 以及 null 類型的。如果我們想直接綁定一個實(shí)例,就需要使用 instance() 方法。

public function instance($abstract, $instance)
{
    $this->removeAbstractAlias($abstract);

    $isBound = $this->bound($abstract);

    unset($this->aliases[$abstract]);

    $this->instances[$abstract] = $instance;

    if ($isBound) {
        $this->rebound($abstract);
    }

    return $instance;
}

在之前我們自己實(shí)現(xiàn)的那個容器類中,在 bind() 方法中直接進(jìn)行了判斷,如果是實(shí)例則直接放到 instances 數(shù)組中,而在 Laravel 中,則是分開了,必須在 instance() 方法中才會將實(shí)例保存到 instances 數(shù)組。

make

最后我們再來看一下 make() 方法,也就是從服務(wù)容器中獲得我們的需要的對象。

public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}

protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
    $abstract = $this->getAlias($abstract);

    if ($raiseEvents) {
        $this->fireBeforeResolvingCallbacks($abstract, $parameters);
    }

    $concrete = $this->getContextualConcrete($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    if (is_null($concrete)) {
        $concrete = $this->getConcrete($abstract);
    }

    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }

    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }

    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    if ($raiseEvents) {
        $this->fireResolvingCallbacks($abstract, $object);
    }

    $this->resolved[$abstract] = true;

    array_pop($this->with);

    return $object;
}

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() 方法中。

public function build($concrete)
{
    if ($concrete instanceof Closure) {
        return $concrete($this$this->getLastParameterOverride());
    }

    try {
        $reflector = new ReflectionClass($concrete);
    } catch (ReflectionException $e) {
        throw new BindingResolutionException("Target class [$concrete] does not exist."0, $e);
    }

    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    $constructor = $reflector->getConstructor();

    if (is_null($constructor)) {
        array_pop($this->buildStack);

        return new $concrete;
    }

    $dependencies = $constructor->getParameters();

    try {
        $instances = $this->resolveDependencies($dependencies);
    } catch (BindingResolutionException $e) {
        array_pop($this->buildStack);

        throw $e;
    }

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}

在 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 中,我們可以看到一段代碼。

$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

這里調(diào)用了 kernel 的 handle() 方法,進(jìn)入 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 的 handle() 方法之后繼續(xù)查看 sendRequestThroughRouter() 方法,在這個方法中調(diào)用了一個 bootstrap() 方法。

// vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

// vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

        $this->make($bootstrapper)->bootstrap($this);

        $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
    }
}

$this->bootstrappers() 返回的就是在 Kernel 中的那個 bootstrappers 屬性,然后通過 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中的 bootstrapWith() 方法來加載這些預(yù)定義的服務(wù)提供者。

不對呀,這里都是預(yù)定義的服務(wù)提供者,我們自定義的那些服務(wù)提供者是在哪里加載的呢?

protected $bootstrappers = [
    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    \Illuminate\Foundation\Bootstrap\BootProviders::class,
];

注意看 RegisterProviders ,bootstrapWith() 方法會直接調(diào)用這些預(yù)定義服務(wù)提供者的 bootstrap() 方法,而 RegisterProviders 中的 bootstrap() 方法只有一行代碼。

public function bootstrap(Application $app)
{
    $app->registerConfiguredProviders();
}

它又回來繼續(xù)調(diào)用 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中的 registerConfiguredProviders() 方法。

public function registerConfiguredProviders()
{
    $providers = Collection::make($this->config['app.providers'])
                    ->partition(function ($provider) {
                        return strpos($provider, 'Illuminate\\') === 0;
                    });

    $providers->splice(10, [$this->make(PackageManifest::class)->providers()]);

    (new ProviderRepository($thisnew Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}

其實(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ù)研究和探索,相信你的收獲一定會更多。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多