1.服務容器
“服務容器”是Lumen框架整個系統功能調度配置的核心,它提供了整個框架運行過程中的一系列服務。“服務容器”就是提供服務(服務可以理解爲系統運行中需要的東西,如:對象、文件路徑、系統配置等)的載體,在系統運行的過程中動態的爲系統提供這些服務。下邊是服務容器工作示意圖:
1.1、服務容器的產生
Lumen框架中,服務容器是由illuminate/container/Container.php中Container類完成的,該類實現了服務容器的核心功能。laravel/lumen-framework/src/Application.php中Application類繼承了該類,實現了服務容器初始化配置和功能拓展。源碼中生成服務容器的代碼是在bootstrap/app.php中:
$app = new Laravel\Lumen\Application(
dirname(__DIR__)
);
也就是Lumen框架在處理每一個請求的時候,都會首先爲這個請求生成一個服務容器,用於容納請求處理需要的服務。
1.2、服務綁定
服務容器生成以後,就可以向其中添加服務,服務綁定可以理解爲一個服務和一個關鍵字綁定,看作鍵值對的形式就是:一個"key" 對應一個服務。要綁定的服務不同,使用的容器中的綁定函數也不同,框架初始化時使用到的是回調函數服務綁定和實例對象服務綁定。回調函數綁定分兩種:一種是普通綁定,另外一種是單例綁定,通過bind()函數中的參數$shared進行區分,項目代碼中的singleton()綁定單例就是bind()函數中$shared參數爲true的情況。源碼如下:
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
單例綁定在整個Lumen生命週期中只會生成並使用一個實例對象。綁定一個實例對象到容器中使用的是instance()函數,綁定之後生成的實例對象會在$instance屬性中記錄。回調函數的綁定在$bindings屬性中記錄。
有一種情況是綁定具體類名稱,實際上也是綁定回調函數的方式,只是回調函數是服務容器根據提供的參數自動生成的,下面章節我們會詳細講解。源碼中有如下代碼:
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
在服務綁定過程中,儘量使用接口名稱和服務進行綁定,這樣可以使得一個具體的功能僅僅和接口實現了耦合,當應用需求變化時可以修改具體類,只要這個類還符合接口規範,程序依然可以健壯的運行。這種“面向接口”編程是一種新的,更有效的解決依賴的編程模式。Lumen框架的接口定義規範都放在/learnLumen/vendor/illuminate/contracts 文件夾下。
1.3、服務解析
服務綁定到容器之後,運行程序就可以隨時從容器中取出服務,這個過程稱爲“服務解析”。服務解析的步驟就是運行程序先獲取到容器對象,然後使用容器對象解析相應的服務。服務解析有常用幾種方式:
- 使用保存服務容器成員屬性,調用make函數進行解析
$this->app->make(App\Service\ExampleService::class);
- 通過全局函數app()來獲取
app(App\Service\ExampleService::class);
- 如果程序使用了Facades外觀,還可以通過靜態方法來解析
\App::make(App\Service\ExampleService::class);
- 服務容器類Container實現了ArrayAccess接口,可以使用數組的方式進行服務解析
app[App\Service\ExampleService::class];
ArrayAccess(數組式訪問)接口非常有用,提供了像訪問數組一樣訪問對象的能力的接口。
使用依賴注入的方式也可以實現服務的自動解析。即在類的構造函數中,使用相應的類提示符,容器會利用自身的反射機制自動解析依賴並實現注入。需要注意的是:在服務註冊以後使用依賴注入功能,則該服務名稱和服務是要遵循一定規範的。即服務名稱一般爲服務生成的類名稱或者接口名稱,只有這樣當服務根據依賴限制查找到服務後生成的實例對象才能滿足這個限制,否則就會報錯。
並不是Lumen框架中所有的類都能實現自動依賴注入的功能,只有“服務容器”創建的類實例才能實現依賴自動注入。
2.控制反轉(Ioc)和依賴注入(DI)
控制反轉是框架設計的一種原則,在很大程度上降低了代碼模塊之間的耦合度,有利於框架維護和拓展。實現控制反轉最常見的方法是“依賴注入”,還有一種方法叫“依賴查找”。控制反轉將框架中解決依賴的邏輯從實現代碼類庫的內部提取到了外部來管理實現。
我們用簡單代碼模擬一下Lumen處理用戶請求的邏輯,框架中要使用到最簡單的Request請求模塊、Response請求模塊,我們使用單例模式簡單實現一下:
//Request模塊實現
class Request
{
static private $instance = null;
private function __construct()
{
}
private function __clone()
{
}
static function getInstance()
{
if (self::$instance == null) self::$instance = new self();
return self::$instance;
}
public function get($key)
{
return $_GET[$key] ? $_GET[$key] : '';
}
public function post($key)
{
return $_POST[$key] ? $_POST[$key] : '';
}
}
//Response模塊實現
class Response
{
static private $instance = null;
private function __construct()
{
}
private function __clone()
{
}
static function getInstance()
{
if (self::$instance == null) self::$instance = new self();
return self::$instance;
}
public function json($data)
{
return json_encode($data);
}
}
我們先來使用“依賴查找”的工廠模式來實現控制反轉,我們需要一個工廠,簡單實現一下:
include_once 'Request.php';
include_once 'Response.php';
include_once 'ExceptionHandler.php';
abstract class Factory
{
static function Create($type, array $params = [])
{
//根據接收到的參數確定要生產的對象
switch ($type) {
case 'request':
return Request::getInstance();
break;
case 'response':
return Response::getInstance();
break;
case 'exception':
return new ExceptionHandler();
break;
}
}
}
接下來就開始實現用戶邏輯,我們首先加入錯誤處理的簡單實現:
//開啓報告代碼中的錯誤處理
class ExceptionHandler
{
public function __construct()
{
error_reporting(-1);
ini_set('display_errors', true);
}
}
我們模擬一個請求用戶列表的邏輯:
include_once 'Factory.php';
Factory::Create('exception');
//用戶邏輯
class UserLogic
{
private $modules = [];
public function __construct(array $modules)
{
foreach ($modules as $key => $module) {
$this->modules[$key] = Factory::Create($module);
}
}
public function getUserList()
{
if ($this->modules['request']->get('path') == 'userlist') {
$userList = [
['name' => '張三', 'age' => 18],
['name' => '李四', 'age' => 22]
];
return $this->modules['response']->json($userList);
}
}
}
try {
$userLogic = new UserLogic(['request' => 'request', 'response' => 'response']);
echo $userLogic->getUserList();
} catch (\Error $e) {
var_dump($e);
exit();
}
可以看到我們使用工廠模式管理依賴的時候,可以在處理業務邏輯外部根據處理請求需要依賴的模塊自行進行注入。比如例子中就注入了request、response模塊。這種模式雖然解決了我們處理邏輯對外部模塊的依賴管理問題,但是並不是太完美,我們的程序只是將原來邏輯對一個個實例子對象的依賴轉換成了工廠對這些實例子對象的依賴,工廠和這些實例子對象之間的耦合還存在,隨着工廠越來越大,用戶邏輯實現越來越複雜,這種“依賴查找”實現控制反轉的模式對於用戶來講依然很痛苦。
接下來我們使用Ioc服務容器來實現依賴注入,下邊先實現一個簡單的服務容器:
class Container
{
//用於裝提供實例的回調函數,真正的容器還會裝實例等其他內容
protected $bindings = [];
//容器共享實例數組(單例)
protected $instances = [];
public function bind($abstract, $concrete = null, $shared = false)
{
if (! $concrete instanceof Closure) {
//如果提供的參數不是回調函數,則產生默認的回調函數
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
public function getBuildings()
{
return $this->bindings;
}
//默認生成實例的回調函數
protected function getClosure($abstract, $concrete)
{
return function ($c) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
//調用的是容器的build或make方法生成實例
return $c->$method($concrete);
};
}
//生成實例對象,首先解決接口和要實例化類之間的依賴關係
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($this->build($concrete));
} else {
$object = $this->make($concrete);
}
return $object;
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
//獲取綁定的回調函數
protected function getConcrete($abstract)
{
if (!isset($this->bindings[$abstract]))
{
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
//實例化一個對象
public function build($concrete)
{
if ($concrete instanceof Closure) {
return $concrete($this);
}
$reflector = new ReflectionClass($concrete);
if (! $reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable.";
}
$constructor = $reflector->getConstructor();
if(is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
//通過反射機制實例化對象時的依賴
protected function getDependencies($parameters)
{
$dependencies = [];
foreach($parameters as $parameter)
{
$dependency = $parameter->getClass();
if(is_null($dependency)) {
$dependencies[] = NULL;
} else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $dependencies;
}
protected function resolveClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
//註冊一個實例並綁定到容器中
public function singleton($abstract, $concrete = null){
$this->bind($abstract, $concrete, true);
}
}
該服務容器可以稱爲Lumen服務容器的簡化版,但是它實現的功能和Lumen服務容器是一樣的,雖然只有一百多行的代碼,但是理解起來有難度,這裏就詳細講解清楚簡化版容器的代碼和原理,接下來章節對Lumen服務容器源碼分析時就僅僅只對方法做簡單介紹。
根據對服務容器介紹章節所講:容器中有兩個關鍵屬性$bindings和$instance,其中$bindings中存在加入到容器中的回調函數,而$instance存放的是容器中綁定的實例對象。我們還知道$singleton方法用來綁定單例對象,其底層只是調用了bind方法而已,只不過$shared屬性爲true,意爲容器中全局共享:
//註冊一個實例並綁定到容器中
public function singleton($abstract, $concrete = null){
$this->bind($abstract, $concrete, true);
}
bind方法的實現也很簡單,只是將用戶指定的服務解析好之後存放入相應的屬性當中:
public function bind($abstract, $concrete = null, $shared = false)
{
if (! $concrete instanceof Closure) {
//如果提供的參數不是回調函數,則產生默認的回調函數
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
Closure是php中的匿名函數類類型。$abstract和$concrete可以抽象理解爲KV鍵值對,K就是$abstract,是服務名;V是$concrete,是服務的具體實現。我們理解容器,首先要將思維從平常的業務邏輯代碼中轉換回來。業務邏輯中操作的一般是用戶數據,而容器中,我們操作的是對象、類、接口之類的,在框架中可稱爲“服務”。如果用戶要綁定的具體實現$concrete不是匿名函數,則調用getClosure方法生成一個匿名函數:
//獲取綁定的回調函數
//默認生成實例的回調函數
protected function getClosure($abstract, $concrete)
{
return function ($c) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
//調用的是容器的build或make方法生成實例
return $c->$method($concrete);
};
}
getClosure是根據用戶傳入的參數來決定調用系統的build和make方法。其中build方法就是構建匿名函數和類實例的關鍵實現,使用了php中的反射機制,解析出類實例:
//實例化一個對象
public function build($concrete)
{
if ($concrete instanceof Closure) {
return $concrete($this);
}
$reflector = new ReflectionClass($concrete);
if (! $reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable.";
}
$constructor = $reflector->getConstructor();
if(is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
build首先判斷參數$concrete是一個匿名函數,就返回調用匿名函數的一個閉包。否則$concrete是一個類,利用反射機制解析類的信息,首先判斷類是否能夠被實例化(例如單例就不能被實例化,容器中的單例是通過屬性$shared來區分的);確保了類能夠被實例化以後,使用getConstructor()判斷類是否定義了構造函數,如果沒有定義構造函數,直接實例化得到一個類的實例。否則就再次調用getParameters獲取構造函數中都傳入了哪些參數(也就是判斷$concrete類都有哪些依賴),getDependencies方法就是來生成$concrete依賴的函數:
//通過反射機制實例化對象時的依賴
protected function getDependencies($parameters)
{
$dependencies = [];
foreach($parameters as $parameter)
{
$dependency = $parameter->getClass();
if(is_null($dependency)) {
$dependencies[] = NULL;
} else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $dependencies;
}
得到了類依賴的實例以後,就調用newInstanceArgs($instances)來生成類的實例。
服務解析函數make主要由build函數實現:
//生成實例對象,首先解決接口和要實例化類之間的依賴關係
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($this->build($concrete));
} else {
$object = $this->make($concrete);
}
return $object;
}
有了服務容器以後,我們就可以使用服務容器來存儲處理請求中需要的服務,並實現服務中的依賴自動注入。不過首先我們需要將Request、Response單例做修改,因爲服務容器對單例的管理,是通過$shared屬性進行設置的。所以Request、Response要能夠被實例化,才能保存到容器的$bindings數組中:
class Request
{
public function __construct()
{
}
public function get($key)
{
return $_GET[$key] ? $_GET[$key] : '';
}
public function post($key)
{
return $_POST[$key] ? $_POST[$key] : '';
}
}
class Response
{
public function __construct()
{
}
public function json($data)
{
return json_encode($data);
}
}
我們再來看使用容器後處理用戶請求的源代碼:
include_once 'Container.php';
include_once 'Request.php';
include_once 'Response.php';
include_once 'ExceptionHandler.php';
$app = new Container();
//綁定錯誤處理
$app->bind('exception', 'ExceptionHandler');
//將請求、響應單例組件添加到容器中
$app->singleton('request', 'Request');
$app->singleton('response', 'Response');
//解析錯誤處理
$app->make('exception');
//用戶邏輯
class UserLogic
{
public $app = null;
public function __construct(Container $app)
{
$this->app = $app;
}
public function getUserList()
{
if ($this->app->make('request')->get('path') == 'userlist') {
$userList = [
['name' => '張三', 'age' => 18],
['name' => '李四', 'age' => 22]
];
return $this->app->make('response')->json($userList);
}
}
}
try {
$userLogic = new UserLogic($app);
echo $userLogic->getUserList();
} catch (\Error $e) {
var_dump($e);
exit();
}
我們還是按照之前的步驟,使用容器將錯誤處理類綁定到容器中,然後解析出來使用。使用singleton方法將Request和Response類綁定到容器中,類型是單例。這樣我們管理服務模塊、實現依賴注入這些問題全都交給容器來做就好了。我們想要什麼樣的服務,就向容器中添加,在需要使用的時候,就利用容器解析使用就可以了。lumen框架中的服務容器是全局的,不需要像例子中一樣,手動注入到邏輯代碼中使用。
3.源碼解析
對於lumen框架來講,服務容器相當於發動機,綁定與解析框架啓動和運行生命週期中所有的服務。它的大致架構如下所示:
3.1、服務容器綁定的方法
- bind綁定
- bindif綁定
- singleton綁定
- instance綁定
- context綁定
- 數組綁定
- 標籤綁定
- extend拓展
- Rebounds與Rebinding
源碼中bind實現代碼如下:
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
從源碼中我們可知:使用bind方法綁定服務,每次都會重新進行綁定(刪除原來的綁定,再重新綁定)。我們類比服務容器中服務的綁定爲KV健值對。key爲接口名稱,而value爲具體的服務實現,之所以推薦使用接口名稱作爲key,是因爲只要開發者遵循相關的接口約束規範,就可以對服務進行拓展和改進,這也是面向接口編程比較新穎之處。另外我們可以看到bind方法核心實現方法是調用rebound方法。
bindif方法核心是調用bind方法,只不過對容器是否綁定服務做了一個判斷:
public function bindIf($abstract, $concrete = null, $shared = false)
{
if (! $this->bound($abstract)) {
$this->bind($abstract, $concrete, $shared);
}
}
singleton是bind方法的一種特例,shared=true表示爲單例綁定:
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
instance是綁定對象實例到容器中(不用使用make進行解析了):
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;
}
數組綁定是Container類繼承了ArrayAccess接口,在offsetSet中調用了bind方法進行註冊:
public function offsetSet($key, $value)
{
$this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
return $value;
});
}
extend方法實現了當原來的類註冊或者實例化出來後,對其進行拓展:
public function extend($abstract, Closure $closure)
{
$abstract = $this->getAlias($abstract);
if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $closure($this->instances[$abstract], $this);
$this->rebound($abstract);
} else {
$this->extenders[$abstract][] = $closure;
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
}
Context綁定是針對於兩個類使用同一個接口,但是我們在類中注入了不同的實現,這時候我們就需要使用when方法了:
public function when($concrete)
{
$aliases = [];
foreach (Arr::wrap($concrete) as $c) {
$aliases[] = $this->getAlias($c);
}
return new ContextualBindingBuilder($this, $aliases);
}
繼續看ContextualBindingBuilder類的源碼我們知道,上下文綁定的基本思路就是$this->app->when()->needs()->give();
比如有幾個控制器分別依賴IlluminateContractsFilesystemFilesystem的不同實現:
$this->app->when(StorageController::class)
->needs(Filesystem::class)
->give(function () {
Storage::class
});//提供類名
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return new Storage();
});//提供實現方式
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return new Storage($app->make(Disk::class));
});//需要依賴注入
有一些場景,我們希望當接口改變以後對已實例化的對象重新做一些改變,這就是rebinding 函數的用途:
public function rebinding($abstract, Closure $callback)
{
$this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;
if ($this->bound($abstract)) {
return $this->make($abstract);
}
}
3.2、服務別名
在服務容器解析之前,Lumen框架會將常用的服務起一些別名,方便系統Facade方法調用和解析。
public function withAliases($userAliases = [])
{
$defaults = [
'Illuminate\Support\Facades\Auth' => 'Auth',
'Illuminate\Support\Facades\Cache' => 'Cache',
'Illuminate\Support\Facades\DB' => 'DB',
'Illuminate\Support\Facades\Event' => 'Event',
'Illuminate\Support\Facades\Gate' => 'Gate',
'Illuminate\Support\Facades\Log' => 'Log',
'Illuminate\Support\Facades\Queue' => 'Queue',
'Illuminate\Support\Facades\Route' => 'Route',
'Illuminate\Support\Facades\Schema' => 'Schema',
'Illuminate\Support\Facades\Storage' => 'Storage',
'Illuminate\Support\Facades\URL' => 'URL',
'Illuminate\Support\Facades\Validator' => 'Validator',
];
if (! static::$aliasesRegistered) {
static::$aliasesRegistered = true;
$merged = array_merge($defaults, $userAliases);
foreach ($merged as $original => $alias) {
class_alias($original, $alias);
}
}
}
...
protected function registerContainerAliases()
{
$this->aliases = [
'Illuminate\Contracts\Foundation\Application' => 'app',
'Illuminate\Contracts\Auth\Factory' => 'auth',
'Illuminate\Contracts\Auth\Guard' => 'auth.driver',
'Illuminate\Contracts\Cache\Factory' => 'cache',
'Illuminate\Contracts\Cache\Repository' => 'cache.store',
'Illuminate\Contracts\Config\Repository' => 'config',
'Illuminate\Container\Container' => 'app',
'Illuminate\Contracts\Container\Container' => 'app',
'Illuminate\Database\ConnectionResolverInterface' => 'db',
'Illuminate\Database\DatabaseManager' => 'db',
'Illuminate\Contracts\Encryption\Encrypter' => 'encrypter',
'Illuminate\Contracts\Events\Dispatcher' => 'events',
'Illuminate\Contracts\Hashing\Hasher' => 'hash',
'log' => 'Psr\Log\LoggerInterface',
'Illuminate\Contracts\Queue\Factory' => 'queue',
'Illuminate\Contracts\Queue\Queue' => 'queue.connection',
'request' => 'Illuminate\Http\Request',
'Laravel\Lumen\Routing\Router' => 'router',
'Illuminate\Contracts\Translation\Translator' => 'translator',
'Laravel\Lumen\Routing\UrlGenerator' => 'url',
'Illuminate\Contracts\Validation\Factory' => 'validator',
'Illuminate\Contracts\View\Factory' => 'view',
];
}
......
lumen服務容器中通過alias方法添加服務別名:
public function alias($abstract, $alias)
{
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
通過getAlias獲得服務的別名:
public function getAlias($abstract)
{
if (! isset($this->aliases[$abstract])) {
return $abstract;
}
if ($this->aliases[$abstract] === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
return $this->getAlias($this->aliases[$abstract]);
通過getAlias我們知道,服務別名是支持遞歸設置的。
3.3、其他函數簡述
服務容器解析一個對象時會觸發resolving和afterResolving函數。分別在之前之後觸發:
public function resolving($abstract, Closure $callback = null)
{
if (is_string($abstract)) {
$abstract = $this->getAlias($abstract);
}
if (is_null($callback) && $abstract instanceof Closure) {
$this->globalResolvingCallbacks[] = $abstract;
} else {
$this->resolvingCallbacks[$abstract][] = $callback;
}
}
public function afterResolving($abstract, Closure $callback = null)
{
if (is_string($abstract)) {
$abstract = $this->getAlias($abstract);
}
if ($abstract instanceof Closure && is_null($callback)) {
$this->globalAfterResolvingCallbacks[] = $abstract;
} else {
$this->afterResolvingCallbacks[$abstract][] = $callback;
}
}
服務容器中有一些裝飾函數,wrap裝飾call,factory裝飾make:
public function call($callback, array $parameters = [], $defaultMethod = null)
{
return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}
......
public function wrap(Closure $callback, array $parameters = [])
{
return function () use ($callback, $parameters) {
return $this->call($callback, $parameters);
};
}
服務容器的解析方法和函數之前已經說過,有幾種常用的方法,這裏就不再一一贅述了。
可以服務容器中flush()方法用於清空容器中所有的服務:
public function flush()
{
$this->aliases = [];
$this->resolved = [];
$this->bindings = [];
$this->instances = [];
$this->abstractAliases = [];
}
Lumen中的服務容器源碼實現非常複雜,但是對其工作原理了解清楚之後,看起來也就有些頭緒了,每個函數所做的工作也可以結合註釋和源碼進行理解了。