swoft2.0源碼剖析01-注解器

swoft2.0源碼剖析01-注解器

前言

本人認(rèn)為學(xué)習(xí)一個(gè)框架正確的方式是先看框架的文檔丐重,再進(jìn)入實(shí)戰(zhàn)動(dòng)手寫(xiě)代碼寫(xiě)一些demo,然后就是看框架的源碼杆查,深入了解框架是怎么啟動(dòng)運(yùn)行的扮惦,整個(gè)框架的流程最好用debug跑一遍,看一下有哪些可以值得學(xué)習(xí)和擴(kuò)展的地方亲桦,比如框架源碼中用到的一些設(shè)計(jì)模式崖蜜,思考為什么用這個(gè)設(shè)計(jì)模式;比如框架源碼中有一些可擴(kuò)展的方法豫领,是文檔中沒(méi)有提及到的二跋;比如想要寫(xiě)框架的擴(kuò)展庫(kù)衫哥,必須要深入了解框架源碼和運(yùn)行流程互例。

下面主要是對(duì)swoft這個(gè)框架注解器組件方面做一個(gè)流程剖析议双。

swoft注解

注解(Annotations) 是Swoft里面很多重要功能的基礎(chǔ)赔蒲。特別是AOP焕刮,IoC容器畸冲、事件監(jiān)聽(tīng)的基礎(chǔ)褪子。
注解的定義是: "附加在數(shù)據(jù)/代碼上的元數(shù)據(jù)(metadata)"
swoft框架可以基于這些元數(shù)據(jù)為代碼提供各種額外功能。

注解 VS 注釋

一般而言紧唱,在編程屆中注解是一種和注釋平行的概念。

  • 注釋提供對(duì)可執(zhí)行代碼的說(shuō)明像捶,單純用于開(kāi)發(fā)人員閱讀,不影響代碼的執(zhí)行殴穴;
  • 而注解往往充當(dāng)著對(duì)代碼的聲明和配置的作用磨取,為可執(zhí)行代碼提供機(jī)器可用的額外信息东臀,在特定的環(huán)境下會(huì)影響程序的執(zhí)行;

php注解與swoft注解

目前PHP沒(méi)有對(duì)注解的官方實(shí)現(xiàn),主流的PHP框架中使用的注解都是借用T_DOC_COMMENT型注釋塊(/**型注釋*/)中的@Tag,定義自己的注解機(jī)制。

Swoft沒(méi)有重新造輪子粟矿,搞一個(gè)新的的注解方案,而是選擇使用Doctrine的注解引擎

Doctrine的注解方案也是基于T_DOC_COMMENT型注釋的福压,Doctrine使用反射獲取代碼的T_DOC_COMMENT型注釋掏秩,并將注釋中的特定類型@Tag映射到對(duì)應(yīng)注解類

如果使用

在過(guò)源碼流程之前或舞,先說(shuō)一下作為swoft核心之一的注解器是怎么使用的,就像我們?nèi)粘i_(kāi)發(fā)寫(xiě)注解一樣蒙幻,只需在類映凳、方法或成員變量上方按規(guī)則添加注解即可,如定義一個(gè)監(jiān)聽(tīng)器:

namespace SwoftTest\Event;

use Swoft\Event\Annotation\Mapping\Listener;
use Swoft\Event\EventHandlerInterface;
use Swoft\Event\EventInterface;

/**
 * Class SendMessageListener
 *
 * @Listener("order.created")
 */
class SendMessageListener implements EventHandlerInterface
{
    /**
     * @param EventInterface $event
     */
    public function handle(EventInterface $event): void
    {
        $pos = __METHOD__;
        echo "handle the event '{$event->getName()}' on the: $pos\n";
    }
}

和laravel必須要寫(xiě)事件監(jiān)聽(tīng)類綁定不一樣邮破,swoft只需要在類注解寫(xiě)上@Listener這個(gè)注解就能將這個(gè)類定義為一個(gè)監(jiān)聽(tīng)器诈豌,并綁定到具體的事件。
order.created是這個(gè)監(jiān)聽(tīng)類綁定的具體事件抒和,任何地方調(diào)用Swoft::trigger("order.created")創(chuàng)建訂單事件能觸發(fā)到這個(gè)發(fā)送消息的監(jiān)聽(tīng)器矫渔,執(zhí)行handle()方法。

實(shí)現(xiàn)注解

那么怎么使得這些注解附有意義呢摧莽?這時(shí)候要引出強(qiáng)大的ReflectionClass反射類庙洼,通過(guò)反射類的getDocComment()方法能獲取某個(gè)類的類注釋,方法和屬性的注釋范嘱。

/**
 * Gets doc comment
 * @link https://php.net/manual/en/reflectionproperty.getdoccomment.php
 * @return string|bool The doc comment if it exists, otherwise <b>FALSE</b>
 * @since 5.1.0
 */
public function getDocComment () {}

不妨設(shè)想一下送膳,程序在開(kāi)始運(yùn)行的時(shí)候(php bin/swoft http start)應(yīng)該是把全部文件都掃描一遍,通過(guò)反射獲取類注解丑蛤,如果類注解為Swoft\Event\Annotation\Mapping\Listener就把該類(SwoftTest\Event\SendMessageListener)和對(duì)應(yīng)的事件名("order.created")綁定到事件管理器叠聋,然后觸發(fā)事件就能找到對(duì)應(yīng)的監(jiān)聽(tīng)器。其實(shí)就是1受裹、先識(shí)別注解 ==> 再到2碌补、讓注解起作用兩個(gè)步驟。

下面來(lái)看一下程序啟動(dòng)的運(yùn)行流程棉饶,可以一邊看代碼一邊看文章厦章,看一下注解是怎么起作用的。

先看命令啟動(dòng)的入口文件 bin/swoft

// Bootstrap
require_once __DIR__ . '/bootstrap.php';

Swoole\Coroutine::set([
    'max_coroutine' => 300000,
]);

// 啟動(dòng)App
(new \App\Application())->run();

主要就是程序的入口照藻,實(shí)例化Application袜啃,并執(zhí)行run()方法,我們知道Application是繼承與SwoftApplication幸缕,Application沒(méi)有構(gòu)造方法群发,自然就會(huì)調(diào)用父類SwoftApplication的構(gòu)造方法,父類的構(gòu)造方法如下:

/**
 * Class constructor.
 *
 * @param array $config
 */
public function __construct(array $config = [])
{
    ......

    // Init application
    $this->init();

    ......
}

前半部分主要是程序初始化的一些校驗(yàn)发乔,日志器的初始化熟妓,省略掉,主要是$this->init()初始化方法

protected function init(): void
{
    ......
    $processors = $this->processors();

    $this->processor = new ApplicationProcessor($this);
    $this->processor->addFirstProcessor(...$processors);
}

......

/**
 * swoft 六大處理器
 * @return ProcessorInterface[]
 */
protected function processors(): array
{
    return [
        new EnvProcessor($this), // 處理環(huán)境變量
        new ConfigProcessor($this), // 處理配置
        new AnnotationProcessor($this), // 處理注解(本文章重點(diǎn))
        new BeanProcessor($this), // 處理容器對(duì)象
        new EventProcessor($this), // 處理事件
        new ConsoleProcessor($this), // 處理命令
    ];
}

初始化里面主要是把swoft六大處理器添加到應(yīng)用程序的主處理器ApplicationProcessor中栏尚,本文章主要解讀的是AnnotationProcessor注解處理器起愈,通過(guò)這個(gè)處理器實(shí)現(xiàn)對(duì)類注解的解析。添加處理器后,Application初始化結(jié)束抬虽。下面再來(lái)調(diào)用的run()方法官觅。

/**
 * Run application
 */
public function run(): void
{
    try {
        if (!$this->beforeRun()) {
            return;
        }
        $this->processor->handle();
    } catch (Throwable $e) {
        ......
    }
}

run()方法主要執(zhí)行了應(yīng)用程序的主處理器ApplicationProcessor的handle()方法,handle()方法把前面添加的六大處理器的handle()方法都執(zhí)行了一遍(代碼省略)阐污,下面主要看AnnotationProcessor注解處理器中的handle()方法缰猴。

/**
 * Handle annotation
 *
 * @return bool
 * @throws Exception
 */
public function handle(): bool
{
    ......

    $app = $this->application;

    AnnotationRegister::load([
        'inPhar'               => IN_PHAR,
        'basePath'             => $app->getBasePath(),
        'notifyHandler'        => [$this, 'notifyHandle'],
        'disabledAutoLoaders'  => $app->getDisabledAutoLoaders(),
        'disabledPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
    ]);

    ......
}

注解處理器handle()方法中調(diào)用了load方法,AnnotationRegister::load()方法調(diào)用了AnnotationResource實(shí)例化對(duì)象的load()方法疤剑,AnnotationResource類用途就是收集整合注解資源的,load方法的主要作用是:

  • 查找目錄中的AutoLoader.php文件闷堡,如果沒(méi)有的話就不解析(所以swoft擴(kuò)展庫(kù)的src目錄必須有AutoLoader.php文件隘膘,否則注解器等功能不能生效)。
  • 解析每個(gè)目錄下每個(gè)文件并收集帶有解析的類或?qū)傩苑椒ā?/li>
/**
 * 遍歷查找目錄中的AutoLoader.php文件杠览,如果沒(méi)有就不解析弯菊,如果有就根據(jù)這個(gè)文件指定的目錄文件解析
 *
 * @throws AnnotationException
 * @throws ReflectionException
 */
public function load(): void
{
    // 獲取composer里面autoload的psr-4映射目錄,包括了app目錄和vendor第三方庫(kù)的目錄
    $prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();

    foreach ($prefixDirsPsr4 as $ns => $paths) {
        ......

        // 循環(huán)每個(gè)目錄踱阿,查找Autoloader.php文件
        foreach ($paths as $path) {
            $loaderFile = $this->getAnnotationClassLoaderFile($path);
            .......
            $loaderClass = $this->getAnnotationLoaderClassName($ns);
            $isEnabled  = true;
            $autoLoader = new $loaderClass();
            .......
            // 如果Autoloader類沒(méi)有允許加載管钳,則不能加載注解,通過(guò)isEnable()控制
            if (isset($this->disabledAutoLoaders[$loaderClass]) || !$autoLoader->isEnable()) {
                $isEnabled = false;

                $this->notify('disabledLoader', $loaderFile);
            } else {
                AnnotationRegister::addAutoLoaderFile($loaderFile);
                $this->notify('addLoaderClass', $loaderClass);

                // 加載并收集注解類(核心)
                $this->loadAnnotation($autoLoader);
            }

            // Storage autoLoader instance to register
            AnnotationRegister::addAutoLoader($ns, $autoLoader, $isEnabled);
        }
    }
}

load()方法已經(jīng)完成了Autoloader.php文件的發(fā)現(xiàn)软舌,這就找到了允許被swoft解析注解的目錄才漆,有了目錄,接下來(lái)就是遍歷目錄中的每個(gè)文件佛点,收集并解析注解醇滥,這一核心流程交給了$this->loadAnnotation($autoLoader)方法去實(shí)現(xiàn)。

/**
 * 循環(huán)解析目錄下每個(gè)文件的注解
 *
 * @param LoaderInterface $loader
 *
 * @throws AnnotationException
 * @throws ReflectionException
 */
private function loadAnnotation(LoaderInterface $loader): void
{
    // 獲取Autoloader類中設(shè)置的目錄
    $nsPaths = $loader->getPrefixDirs();

    foreach ($nsPaths as $ns => $path) {
        // 迭代生成目錄下文件迭代器超营,然后遍歷每個(gè)文件
        $iterator = DirectoryHelper::recursiveIterator($path);

        foreach ($iterator as $splFileInfo) {
            ......
            $suffix    = sprintf('.%s', $this->loaderClassSuffix);
            $pathName  = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
            $className = sprintf('%s%s', $ns, $pathName);
            // 解析某個(gè)類鸳玩,查看某個(gè)類有沒(méi)有類注解,方法注解演闭,屬性注解等不跟。
            $this->parseAnnotation($ns, $className);
        }
    }
}

/**
 * 解析某個(gè)類的注解
 *
 * @param string $namespace
 * @param string $className
 *
 * @throws AnnotationException
 * @throws ReflectionException
 */
private function parseAnnotation(string $namespace, string $className): void
{
    // **核心**:實(shí)例化某類的ReflectionClass類,比如說(shuō)上面的SwoftTest\Event\SendMessageListener類
    $reflectionClass = new ReflectionClass($className);

    // Fix ignore abstract
    if ($reflectionClass->isAbstract()) {
        return;
    }
    // 根據(jù)反射類ReflectionClass解析某個(gè)類并查找某個(gè)類注解米碰,返回了某個(gè)類整合完的注解(數(shù)組形式)
    $oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);

    // 如果某個(gè)類整合完的注解不為空窝革,就注冊(cè)到AnnotationRegister類中
    if (!empty($oneClassAnnotation)) {
        AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
    }
}

核心流程:實(shí)例化了某類的ReflectionClass類,比如說(shuō)上面的SwoftTest\Event\SendMessageListener類见间,然后把反射類傳遞給parseOneClassAnnotation()方法去處理聊闯。

/**
 * 解析某個(gè)類的注解
 *
 * @param ReflectionClass $reflectionClass
 *
 * @return array
 * @throws AnnotationException
 * @throws ReflectionException
 */
private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
{
    // Annotation reader 注解閱讀器
    $reader    = new AnnotationReader();
    $className = $reflectionClass->getName();

    $oneClassAnnotation = [];
    // $reader獲取類注解
    $classAnnotations   = $reader->getClassAnnotations($reflectionClass);

    // Register annotation parser 注冊(cè)注解的解析器,這里的解析器不是getDocComment()米诉,而是把上面例子中監(jiān)聽(tīng)器和時(shí)間綁定的解析器菱蔬。通常在src\Annotation\Parser目錄中。
    foreach ($classAnnotations as $classAnnotation) {
        if ($classAnnotation instanceof AnnotationParser) {
            // * 如果是AnnotationParser解析類,則把該類注冊(cè)到AnnotationRegister類的$parsers(解析器)屬性中拴泌,這個(gè)解析類后文作用重大魏身,用來(lái)讓注解真正起作用的,一個(gè)注解類對(duì)應(yīng)一個(gè)解析類
            $this->registerParser($className, $classAnnotation);

            return [];
        }
    }

    // Class annotation
    if (!empty($classAnnotations)) {
        $oneClassAnnotation['annotation'] = $classAnnotations;
        $oneClassAnnotation['reflection'] = $reflectionClass;
    }

    // 獲取類屬性 => 遍歷類屬性 => 獲取屬性注解
    $reflectionProperties = $reflectionClass->getProperties();
    foreach ($reflectionProperties as $reflectionProperty) {
        $propertyName        = $reflectionProperty->getName();
        // $reader獲取屬性注解
        $propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);

        if (!empty($propertyAnnotations)) {
            $oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
            $oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
        }
    }

    // 獲取類方法 => 遍歷類方法 => 獲取方法注解
    $reflectionMethods = $reflectionClass->getMethods();
    foreach ($reflectionMethods as $reflectionMethod) {
        $methodName        = $reflectionMethod->getName();
        // $reader獲取方法注解
        $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);

        if (!empty($methodAnnotations)) {
            $oneClassAnnotation['methods'][$methodName]['annotation'] = $methodAnnotations;
            $oneClassAnnotation['methods'][$methodName]['reflection'] = $reflectionMethod;
        }
    }

    $parentReflectionClass = $reflectionClass->getParentClass();
    if ($parentReflectionClass !== false) {
        $parentClassAnnotation = $this->parseOneClassAnnotation($parentReflectionClass);
        if (!empty($parentClassAnnotation)) {
            $oneClassAnnotation['parent'] = $parentClassAnnotation;
        }
    }

    return $oneClassAnnotation;
}

AnnotationParser解析類蚪腐,與注解類一一對(duì)應(yīng)箭昵,有Swoft\Event\Annotation\Mapping\Listener注解類就有對(duì)應(yīng)的Swoft\Event\Annotation\Parser\ListenerParser解析類,一般都存放在app或者庫(kù)src目錄下的Annotation目錄中回季。

$reader = new AnnotationReader()注解閱讀器是引用了Doctrine注解引擎這個(gè)包里面的類家制。

parseOneClassAnnotation()方法解析某個(gè)類的注解,并根據(jù)類泡一、屬性颤殴、方法整合到了$oneClassAnnotation數(shù)組中,并返回鼻忠。AnnotationReader類能解析(類涵但、屬性、方法)注解帖蔓,下面看看這個(gè)類是怎么通過(guò)getClassAnnotations()方法獲取注解的矮瘟。

/**
 * {@inheritDoc}
 */
public function getClassAnnotations(ReflectionClass $class)
{
    $this->parser->setTarget(Target::TARGET_CLASS);
    $this->parser->setImports($this->getClassImports($class));
    $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
    $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);

    return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
    }

使用ReflectionClass的getDocComment()方法獲取類注釋,再傳遞給this->parser塑娇,`this->parser->parse()`方法根據(jù)@關(guān)鍵字把注解格式化為數(shù)組形式澈侠,數(shù)組里面是具體的注解類實(shí)例化對(duì)象例如:

/**
 * Class SendMessageListener
 *
 * @since 2.0
 *
 * @Listener(DbEvent::MODEL_SAVED)
 */
 class SendMessageListener{
    ......
 }

經(jīng)過(guò)parser()處理后變成如下形式,由于只有一個(gè)注解@Listener埋酬,所以數(shù)組只有一個(gè)元素埋涧。

array(1) {
  [0]=>
  object(Swoft\Event\Annotation\Mapping\Listener)#1479 (2) {
    ["event":"Swoft\Event\Annotation\Mapping\Listener":private]=>
    string(17) "swoft.model.saved"
    ["priority":"Swoft\Event\Annotation\Mapping\Listener":private]=>
    int(0)
  }
}

同樣的方式獲取屬性的注解,方法的注解奇瘦,并組裝成oneClassAnnotation數(shù)組棘催,然后通過(guò)`AnnotationRegister::registerAnnotation()`方法,將類名(例中的`SwoftTest\Event\SendMessageListener`)耳标、命名空間醇坝、和生成的注解數(shù)組oneClassAnnotation注冊(cè)到然后通過(guò)AnnotationRegister類的$annotations屬性中。

整個(gè)目錄文件掃描的流程完成后次坡,最終會(huì)把所有的注解(格式化后 => 注解類實(shí)例化對(duì)象)添加在AnnotationRegister類的$annotations屬性中呼猪。

/**
 * Class AnnotationRegister
 *
 * @since 2.0.0
 */
final class AnnotationRegister
{
    /**
     * @var array
     *
     * @example
     * [
     *     // 命名空間 SwoftTest\Event
     *    'loadNamespace' => [
     *         // 類名,例子中的SwoftTest\Event\SendMessageListener
     *        'className' => [
     *              // 類注解
     *             'annotation' => [
     *                  // 例子中的Swoft\Event\Annotation\Mapping\Listener注解類對(duì)象
     *                  new ClassAnnotation(),
     *                  new ClassAnnotation(),
     *                  new ClassAnnotation(),
     *             ]
     *             'reflection' => new ReflectionClass(),
     *             // 屬性注解
     *             'properties' => [
     *                  'propertyName' => [
     *                      'annotation' => [
     *                          new PropertyAnnotation(),
     *                          new PropertyAnnotation(),
     *                          new PropertyAnnotation(),
     *                      ]
     *                     'reflection' => new ReflectionProperty(),
     *                  ]
     *             ],
     *             // 方法注解
     *            'methods' => [
     *                  'methodName' => [
     *                      'annotation' => [
     *                          new MethodAnnotation(),
     *                          new MethodAnnotation(),
     *                          new MethodAnnotation(),
     *                      ]
     *                     'reflection' => new ReflectionFunctionAbstract(),
     *                  ]
     *            ]
     *        ]
     *    ]
     * ]
     */
    private static $annotations = [];
}

一般來(lái)說(shuō)砸琅,每個(gè)不同的注解類都會(huì)有不同的屬性宋距,比如Swoft\Event\Annotation\Mapping\Listener注解類就保存了事件名和優(yōu)先級(jí)屬性,而Swoft\Bean\Annotation\Mapping\Bean這個(gè)注解類就保存了名稱症脂、作用域谚赎、別名等屬性淫僻,這些屬性時(shí)在解析類中獲取的,解析類的作用下文說(shuō)壶唤。

至此雳灵,AnnotationProcessor任務(wù)完成。但是有個(gè)疑問(wèn)的是闸盔,上面的流程只是把注解格式化出來(lái)(步驟1識(shí)別注解)悯辙,具體怎么樣讓注解起作用好像是還沒(méi)有涉及到,那得繼續(xù)看下一個(gè)處理器BeanProcessor的handle()方法了迎吵。

先說(shuō)一下BeanProcessor處理器的功能躲撰,這個(gè)處理器的功能如下:

  • Bean 就是一個(gè)類的一個(gè)對(duì)象實(shí)例。 容器Container就是一個(gè)巨大的工廠击费,用于存放和管理 Bean 生命周期茴肥。所以這個(gè)處理器是用來(lái)生成Bean并放入容器中的。
  • 把a(bǔ)pp目錄下的bean.php文件的數(shù)組AutoLoader的beans()方法返回的數(shù)組合并再實(shí)例化后放入容器Container中荡灾。
  • 把用了@Bean注解的類實(shí)例化后放入Container中,所以必須要讓注解起作用后才能進(jìn)行Bean的實(shí)例化瞬铸。
/**
 * Class BeanProcessor
 *
 * @since 2.0
 */
class BeanProcessor extends Processor
{
    /**
     * Handle bean
     *
     * @return bool
     * @throws ReflectionException
     * @throws AnnotationException
     */
    public function handle(): bool
    {
        ......

        $handler     = new BeanHandler();
        // 獲取bean.php文件的數(shù)組和AutoLoader的beans()方法返回的數(shù)組
        $definitions = $this->getDefinitions();
        // 獲取$parsers(解析類)
        $parsers     = AnnotationRegister::getParsers();
        // 獲取$annotations(所有格式化后注解的結(jié)果批幌,注解類)
        $annotations = AnnotationRegister::getAnnotations();

        // 把上面獲取到的都添加到BeanFactory的屬性中
        BeanFactory::addDefinitions($definitions);
        BeanFactory::addAnnotations($annotations);
        BeanFactory::addParsers($parsers);
        BeanFactory::setHandler($handler);
        // Bean工廠初始化
        BeanFactory::init();

        ......
    }
    ......
}

/**
 * Class BeanFactory
 *
 * @since 2.0
 */
class BeanFactory
{
    /**
     * Init bean container
     *
     * @return void
     * @throws AnnotationException
     * @throws ReflectionException
     */
    public static function init(): void
    {
        // 調(diào)用容器的初始化方法
        Container::getInstance()->init();
    }
    ......
}

/**
 * Class Container
 */
class Container implements ContainerInterface
{

    /**
     * Init
     *
     * @throws AnnotationException
     * @throws ReflectionException
     */
    public function init(): void
    {
        // 解析注解
        $this->parseAnnotations();

        // 解析Bean對(duì)象
        $this->parseDefinitions();

        // 實(shí)例化Bean對(duì)象
        $this->initializeBeans();
    }
}

我們重點(diǎn)看$this->parseAnnotations()這個(gè)方法,這個(gè)方法是解析注解類的嗓节,讓注解起作用的(步驟2)荧缘,下面的是解析和實(shí)例化Bean,和注解類怎么起作用的無(wú)關(guān)拦宣,就不理它了截粗。至于為什么把解析注解類的流程放在BeanProcessor處理器上而不放在AnnotationProcessor處理器上,可能是解析類處理完要返回一個(gè)結(jié)果提供給Bean鸵隧,為什么這么做绸罗,猜想是要兼容很多解析類有關(guān)吧,具體我也不太明白豆瘫,繼續(xù)看代碼珊蟀。

/**
 * Class Container
 */
class Container implements ContainerInterface
{

    /**
     * Parse annotations
     *
     * @throws AnnotationException
     */
    private function parseAnnotations(): void
    {
        $annotationParser = new AnnotationObjParser(
            $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
        );
        // 實(shí)例化AnnotationObjParser對(duì)象,用這個(gè)對(duì)象去解析注解類
        $annotationData  = $annotationParser->parseAnnotations($this->annotations, $this->parsers);

        [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
    }
}


/**
 * Class AnnotationParser
 *
 * @since 2.0
 */
class AnnotationObjParser extends ObjectParser
{
    /**
     * Parse annotations
     *
     * @param array $annotations
     * @param array $parsers
     *
     * @return array
     * @throws AnnotationException
     */
    public function parseAnnotations(array $annotations, array $parsers): array
    {
        $this->parsers     = $parsers;
        $this->annotations = $annotations;

        foreach ($this->annotations as $loadNameSpace => $classes) {
            foreach ($classes as $className => $classOneAnnotations) {
                $this->parseOneClassAnnotations($className, $classOneAnnotations);
            }
        }

        return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
    }
}

AnnotationObjParser類的parseAnnotations方法循環(huán)了所有的注解annotations外驱,調(diào)用了parseOneClassannotations()育灸,并且把類名className(SwoftTest\Event\SendMessageListener)和這個(gè)類的所有注解類$classOneAnnotations (包括類注解,方法注解昵宇,屬性注解)作為參數(shù)傳遞給了這個(gè)方法磅崭。

/**
 * 解析某類的所有注解
 *
 * @param string $className
 * @param array  $classOneAnnotations
 *
 * @throws AnnotationException
 */
private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
{
    ......

    // Parse class annotations
    $classAnnotations = $classOneAnnotations['annotation'];
    $reflectionClass  = $classOneAnnotations['reflection'];

    $classAry = [
        $className,
        $reflectionClass,
        $classAnnotations
    ];

    // 解析類注解
    $objectDefinition = $this->parseClassAnnotations($classAry);

    // 解析屬性注解
    $propertyInjects        = [];
    $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
    foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
        $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
        $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
        if ($propertyInject) {
            $propertyInjects[$propertyName] = $propertyInject;
        }
    }

    // 解析方法注解
    $methodInjects        = [];
    $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
    foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
        $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];

        $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
        if ($methodInject) {
            $methodInjects[$methodName] = $methodInject;
        }
    }

    ......
}

我們只看怎么解析類注解$this->parseClassAnnotations($classAry)

/**
 * @param array $classAry
 *
 * @return ObjectDefinition|null
 */
private function parseClassAnnotations(array $classAry): ?ObjectDefinition
{
    [, , $classAnnotations] = $classAry;

    $objectDefinition = null;
    foreach ($classAnnotations as $annotation) {
        $annotationClass = get_class($annotation);
        if (!isset($this->parsers[$annotationClass])) {
            continue;
        }

        // 去解析類數(shù)組里面根據(jù)注解類名找到對(duì)應(yīng)的解析類名(前面說(shuō)了注解類和解析類一一對(duì)應(yīng)的)
        $parserClassName  = $this->parsers[$annotationClass];
        // 根據(jù)解析類名獲取示例化后的解析類(例子中是Swoft\Event\Annotation\Parser\ListenerParser)
        $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
        // 調(diào)用解析類的parse()
        $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);

        ......
    }

    return $objectDefinition;
}

調(diào)用解析類的parse()方法瓦哎,例子中就是調(diào)用Swoft\Event\Annotation\Parser\ListenerParser這個(gè)解析類的parse()方法砸喻,這個(gè)方法就是實(shí)現(xiàn)了步驟2讓注解起作用柔逼,具體看一下parse()方法。

class ListenerParser extends Parser
{
    /**
     * @param int      $type
     * @param Listener $annotation
     *
     * @return array
     * @throws AnnotationException (注解類恩够,例子中的Swoft\Event\Annotation\Mapping\Listener)
     */
    public function parse(int $type, $annotation): array
    {
        if ($type !== self::TYPE_CLASS) {
            throw new AnnotationException('`@Listener` must be defined on class!');
        }

        // 注冊(cè)監(jiān)聽(tīng)器卒落,key為為SwoftTest\Event\SendMessageListener,值為["order.created" => 0]
        ListenerRegister::addListener($this->className, [
            // event name => listener priority
            $annotation->getEvent() => $annotation->getPriority()
        ]);

        return [$this->className, $this->className, Bean::SINGLETON, ''];
    }
}

ListenerParser類的parse()方法把注解類(Swoft\Event\Annotation\Mapping\Listener)實(shí)例化對(duì)象中保存的事件名和優(yōu)先級(jí)注冊(cè)到了ListenerRegister類的$listeners屬性中蜂桶,從而使得SwoftTest\Event\SendMessageListener和"order.created"綁定了關(guān)系儡毕,后續(xù)程序中觸發(fā)了order.created事件就能找到對(duì)應(yīng)的監(jiān)聽(tīng)器了。

通過(guò)識(shí)別注解 ==> 到解析注解這么一個(gè)流程扑媚,類注解成功綁定了事件和監(jiān)聽(tīng)器了腰湾。

自定義注解

swoft框架的注解流程講解完了,如果要自定義一個(gè)注解怎么做應(yīng)該也清晰多了
主要是編寫(xiě)注解類和解析類:
注解類寫(xiě)在App\Annotation\Mapping目錄疆股,比如編寫(xiě)要編寫(xiě)一個(gè)門(mén)面Facades的注解類App\Annotation\Mapping\FacadesAnnotation類费坊,讓類變成門(mén)面類(不了解門(mén)面的可以看一下laravel門(mén)面的文檔)。

namespace App\Annotation\Mapping;

use Doctrine\Common\Annotations\Annotation\Attribute;
use Doctrine\Common\Annotations\Annotation\Attributes;

/**
 * Class Facades
 *
 * @since 2.0
 *
 * @Annotation  //聲明這是一個(gè)注解類
 * @Target("CLASS") //聲明這個(gè)注解只可用在class級(jí)別的注釋中
 * @Attributes({
 *     @Attribute("alias",type="string")
 * })
 */
class Facades
{
    /**
     * @var string
     */
    private $alias = '';

    /**
     * StringType constructor.
     *
     * @param array $values
     */
    public function __construct(array $values)
    {
        if (isset($values['value'])) {
            $this->message = $values['value'];
        }
        if (isset($values['alias'])) {
            $this->alias = $values['alias'];
        }
    }

    /**
     * @return string
     */
    public function getAlias(): string
    {
        return $this->alias;
    }
}

在程序任何有Autoload.php文件的目錄下的類都能在類注解上按格式寫(xiě)上自定義的注解類(記得要use 注解類旬痹,phpstorm有注解的插件可以直接引入)比如:

use App\Annotation\Mapping\Facades

/**
 * Class Calculate
 *
 * @Facades()
 */
 class Calculate{

    /**
     * 執(zhí)行
     *
     * @return mixed
     * @throws Exception
     */
    public function execute()
    {
        ......
    }
 }

解析類寫(xiě)在App\Annotation\Parser目錄下附井,編寫(xiě)App\Annotation\Parser\FacadesParser類:

<?php declare(strict_types=1);
/**
 * This file is part of Swoft.
 *
 * @link     https://swoft.org
 * @document https://swoft.org/docs
 * @contact  group@swoft.org
 * @license  https://github.com/swoft-cloud/swoft/blob/master/LICENSE
 */

namespace App\Annotation\Parser;

use ReflectionException;
use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
use Swoft\Annotation\Annotation\Parser\Parser;
use App\Annotation\Mapping\Facades;
use Swoft\Validator\Exception\ValidatorException;
use Swoft\Validator\ValidatorRegister;

/**
 * Class FacadesParser
 *
 * @AnnotationParser(annotation=Facades::class) // 參數(shù)值寫(xiě)上注解類
 */
class FacadesParser extends Parser
{
    /**
     * @param int $type
     * @param object $annotationObject
     *
     * @return array
     * @throws ReflectionException
     * @throws ValidatorException
     */
    public function parse(int $type, $annotationObject): array
    {
        // 可以獲取到目標(biāo)類Calculate,用$this->className獲取
        // 可以獲取到注解類對(duì)象$annotationObject
        // 這里是把目標(biāo)類Calculate怎么變成門(mén)面的流程两残,我也沒(méi)有實(shí)現(xiàn)永毅,有興趣的可以自己寫(xiě)一個(gè)
        return [];
    }
}

以上就是Swoft注解器的源碼流程剖析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末人弓,一起剝皮案震驚了整個(gè)濱河市沼死,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崔赌,老刑警劉巖意蛀,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異健芭,居然都是意外死亡县钥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)慈迈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)魁蒜,“玉大人,你說(shuō)我怎么就攤上這事吩翻《悼矗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵狭瞎,是天一觀的道長(zhǎng)细移。 經(jīng)常有香客問(wèn)我,道長(zhǎng)熊锭,這世上最難降的妖魔是什么弧轧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任雪侥,我火速辦了婚禮,結(jié)果婚禮上精绎,老公的妹妹穿的比我還像新娘速缨。我一直安慰自己,他們只是感情好代乃,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布旬牲。 她就那樣靜靜地躺著,像睡著了一般搁吓。 火紅的嫁衣襯著肌膚如雪原茅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天堕仔,我揣著相機(jī)與錄音擂橘,去河邊找鬼。 笑死摩骨,一個(gè)胖子當(dāng)著我的面吹牛通贞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恼五,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼昌罩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了唤冈?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤银伟,失蹤者是張志新(化名)和其女友劉穎你虹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體彤避,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡傅物,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了琉预。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片董饰。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡塔猾,死狀恐怖家浇,靈堂內(nèi)的尸體忽然破棺而出隐绵,到底是詐尸還是另有隱情混萝,我是刑警寧澤淘正,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布桐猬,位于F島的核電站茫船,受9級(jí)特大地震影響愿棋,放射性物質(zhì)發(fā)生泄漏近速。R本人自食惡果不足惜诈嘿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一堪旧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奖亚,春花似錦淳梦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至李滴,卻和暖如春螃宙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背所坯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工谆扎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芹助。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓堂湖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親状土。 傳聞我的和親對(duì)象是個(gè)殘疾皇子无蜂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容