资讯专栏INFORMATION COLUMN

Swoft 源码剖析 - Swoft 中的注解机制

zzbo / 2059人阅读

摘要:中的注解注解是里面很多重要功能特别是,容器的基础。主流的框架中使用的注解都是借用型注释块型注释中的定义自己的注解机制。在中是注解信息的最终装载容器。使用的信息构造实例或获取现有实例以上就是注解机制的整体实现了。源码剖析系列目录

作者:bromine
链接:https://www.jianshu.com/p/ef7...
來源:简书
著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。
Swoft Github: https://github.com/swoft-clou...

PHP中的注解

注解(Annotations)是Swoft里面很多重要功能特别是AOP,IoC容器的基础。
注解的定义是:“附加在数据/代码上的元数据(metadata)。”框架可以基于这些元信息为代码提供各种额外功能。

以另一个框架PHPUnit为例,注解@dataProvider声明一个方法作为测试用例方法的数据提供器。当PHPUnit框架执行到某一个测试用例方法时,会迭代该数据提供器,并将其返回的数据作为参数传入测试用例方法,为测试用例方法提供一套用例所需的测试数据。

//摘自phpseclib库的单元测试
public function formatLogDataProvider()
{
    return array(
        array(
            //该参数会作为$message_log参数传到testFormatLog()测试用例方法中
            array("hello world"),            
            array("<--"),               //$message_number_log     
            "<--
00000000  68:65:6c:6c:6f:20:77:6f:72:6c:64                 hello world

"//$expected
        ),
        array(
            array("hello", "world"),
            array("<--", "<--"),
            "<--
00000000  68:65:6c:6c:6f                                   hello

" .
            "<--
00000000  77:6f:72:6c:64                                   world

"
        ),
    );
}

/**
 * @dataProvider formatLogDataProvider
 */
public function testFormatLog(array $message_log, array $message_number_log, $expected)
{
     $ssh = $this->createSSHMock();

    $result = $ssh->_format_log($message_log, $message_number_log);
    $this->assertEquals($expected, $result);
}

一般而言,在编程届中注解是一种和注释平行的概念。
注释提供对可执行代码的说明,单纯用于开发人员阅读,不影响代码的执行;而注解往往充当着对代码的声明和配置的作用,为可执行代码提供机器可用的额外信息,在特定的环境下会影响程序的执行。

但是由于官方对PHP的Annotation方案迟迟没有达成一致(最新进展可以在 PHP: rfc 看到),目前PHP没有对注解的官方实现。主流的PHP框架中使用的注解都是借用T_DOC_COMMENT型注释块(/*型注释/)中的@Tag,定义自己的注解机制。

想对PHP注解的发展史要有更多了解的朋友可以参考Rafael Dohms的这个PPT:https://www.slideshare.net/rd...

Doctrine注解引擎

Swoft没有重新造轮子,搞一个新的的注解方案,而是选择使用 Doctrine的注解引擎

Doctrine的注解方案也是基于T_DOC_COMMENT型注释的,Doctrine使用反射获取代码的T_DOC_COMMENT型注释,并将注释中的特定类型@Tag映射到对应注解类。为此,Swoft首先要为每一个框架自定义的注解定义注解类。

注解定义

@Breaker注解的注解类定义如下。

name = $values["value"];
        }
        if (isset($values["name"])) {
            $this->name = $values["name"];
        }
    }

     //按需写的getter setter code....
}

简单几行,一个@Breaker的注解类的定义工作就完成了。

注解类加载器的注册

在框架的bootstap阶段,swoft会扫描所有的PHP源码文件获取并解析注解信息。

使用Doctrine首先需要提供一个类的自动加载方法,这里直接使用了swoft当前的类加载器。Swoft的类加载器由Composer自动生成,这意味着注解类只要符合PSR-4规范即可自动加载。

//SwoftBeanResourceAnnotationResource.php
/**
 * 注册加载器和扫描PHP文件
 *
 * @return array
 */
protected function registerLoaderAndScanBean()
{
        // code code....

        AnnotationRegistry::registerLoader(function ($class) {
            if (class_exists($class) || interface_exists($class)) {
                return true;
            }

            return false;
        });

        // code code....

    return array_unique($phpClass);
}
使用Doctrine获取注解对象

扫描各源码目录获取PHP类后,Sworft会遍历类列表加载类,获取类级别,方法级别,属性级别的所有注解对象。结果存放在AnnotationResource的$annotations成员中。

//SwoftBeanResourceAnnotationResource.php
/**
 * 解析bean注解
 *
 * @param string $className
 * @return null
 */
public function parseBeanAnnotations(string $className)
{
    if (!class_exists($className) && !interface_exists($className)) {
        return null;
    }

    // 注解解析器
    $reader           = new AnnotationReader();
    $reader           = $this->addIgnoredNames($reader);//跳过Swoft内部注解
    $reflectionClass  = new ReflectionClass($className);
    $classAnnotations = $reader->getClassAnnotations($reflectionClass);

    // 没有类注解不解析其它注解
    if (empty($classAnnotations)) {
        return;
    }

    foreach ($classAnnotations as $classAnnotation) {
        $this->annotations[$className]["class"][get_class($classAnnotation)] = $classAnnotation;
    }

    // 解析属性
    $properties = $reflectionClass->getProperties();
    foreach ($properties as $property) {
        if ($property->isStatic()) {
            continue;
        }
        $propertyName        = $property->getName();
        $propertyAnnotations = $reader->getPropertyAnnotations($property);
        foreach ($propertyAnnotations as $propertyAnnotation) {
            $this->annotations[$className]["property"][$propertyName][get_class($propertyAnnotation)] = $propertyAnnotation;
        }
    }

    // 解析方法
    $publicMethods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
    foreach ($publicMethods as $method) {
        if ($method->isStatic()) {
            continue;
        }

        $methodName = $method->getName();

        // 解析方法注解
        $methodAnnotations = $reader->getMethodAnnotations($method);

        foreach ($methodAnnotations as $methodAnnotation) {
            $this->annotations[$className]["method"][$methodName][get_class($methodAnnotation)][] = $methodAnnotation;
        }
    }
}
注解的解析

doctrine完成的功能仅仅是将注解映射到将用@Annotation声明的注解类。swoft需要自行处理注解对象获取注解中的信息。这一步有两个重要功能:

扫描搜集Bean的所有信息包括Bean名,类名以及该Bean各个需要注入的属性信息等,存放到ObjectDefinition数组中。

//SwoftBeanWrapperAbstractWrapper.php
/**
 * 封装注解
 *
 * @param string $className
 * @param array  $annotations 注解3剑客,包含了类级别,方法级别,属性级别的注解对象,注解解析流程你会一直看到他
 *
 * @return array|null
 */
public function doWrapper(string $className, array $annotations)
{
    $reflectionClass = new ReflectionClass($className);

    // 解析类级别的注解
    $beanDefinition = $this->parseClassAnnotations($className, $annotations["class"]);

    //code...

    // parser bean annotation
    list($beanName, $scope, $ref) = $beanDefinition;

    // 初始化Bean结构,并填充该Bean的相关信息
    $objectDefinition = new ObjectDefinition();
    $objectDefinition->setName($beanName);
    $objectDefinition->setClassName($className);
    $objectDefinition->setScope($scope);
    $objectDefinition->setRef($ref);

    if (!$reflectionClass->isInterface()) {
        // 解析属性,并获取属性相关依赖注入的信息
        $properties = $reflectionClass->getProperties();
        $propertyAnnotations = $annotations["property"]??[];
        $propertyInjections = $this->parseProperties($propertyAnnotations, $properties, $className);
        $objectDefinition->setPropertyInjections($propertyInjections);//PropertyInjection对象
    }

    // 解析方法
    $publicMethods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
    $methodAnnotations = $annotations["method"] ??[];
    $this->parseMethods($methodAnnotations, $className, $publicMethods);
   
    return [$beanName, $objectDefinition];
}

在注解解析时Parser会调用相关的Collector搜集功能所需的信息,譬如进行事件注册。

举个例子,BootstrapParser的解析仅仅就是搜集注解。Collector在Swoft中是注解信息的最终装载容器。一般而言@XXXX注解对应的Parser和Collect就是XXXXParser和XXXXCollect,知道这个惯例会大大方便你对Swoft源码的阅读。

//SwoftBeanParserBootstrapParser.php
class BootstrapParser extends AbstractParser
{
    /**
     * @param string    $className
     * @param Bootstrap $objectAnnotation
     * @param string    $propertyName
     * @param string    $methodName
     * @param mixed     $propertyValue
     *
     * @return array
     */
    public function parser(string $className, $objectAnnotation = null, string $propertyName = "", string $methodName = "", $propertyValue = null)
    {
        $beanName = $className;
        $scope    = Scope::SINGLETON;

        BootstrapCollector::collect($className, $objectAnnotation, $propertyName, $methodName, $propertyValue);

        return [$beanName, $scope, ""];
    }
}

由于框架执行前必须完整的获取各种注解到Collertor和生成Bean定义集合,所以Swoft是不进行lazyload的。

注解的使用

现在我们终于可以用一个的例子来讲解注解是如何运行。InitMbFunsEncoding是一个实现了Bootable的类,他的作用是在应用启动时候设定系统的编码。但是仅仅实现了Bootable接口并不会让框架在启动时自动调用他。
因此我们需要InitMbFunsEncoding为添加一个@Bootstrap(order=1)类注解,让他成为一个Bootstrap型的Bean。

//SwoftBootstrapBoots.InitMbFunsEncoding.php

我们在上文已经提过框架启动时会扫描PHP源码

将Bean的定义信息存放到ObjectDefinition数组中

将注解信息存放到各个Collector中

因此在框架的Bootstrap阶段,可以从BootstrapCollector中直接获取所有@Bootstrap型的Bean,实例化并Bean执行。

 $name){
        //使用Bean的ObjectDefinition信息构造实例或获取现有实例
        /* @var Bootable $bootstrap*/
        $bootstrap = App::getBean($bootstrapBeanName);
        $bootstrap->bootstrap();
    }
}

//code ...

以上就是Swoft注解机制的整体实现了。

Swoft源码剖析系列目录:https://segmentfault.com/a/11...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30704.html

相关文章

  • Swoft 源码剖析 - 目录

    摘要:作者链接來源简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。同时顺手整理个人对源码的相关理解,希望能够稍微填补学习领域的空白。系列文章只会节选关键代码辅以思路讲解,请自行配合源码阅读。 作者:bromine链接:https://www.jianshu.com/p/2f6...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。Swoft...

    qpwoeiru96 评论0 收藏0
  • Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)

    摘要:和服务关系最密切的进程是中的进程组,绝大部分业务处理都在该进程中进行。随后触发一个事件各组件通过该事件进行配置文件加载路由注册。事件每个请求到来时仅仅会触发事件。服务器生命周期和服务基本一致,详情参考源码剖析功能实现 作者:bromine链接:https://www.jianshu.com/p/4c0...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。S...

    张汉庆 评论0 收藏0
  • Swoft 源码剖析 - 代码自动更新机制

    摘要:作者链接來源简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。文件重载管理进程注册了一个名为的该进程会在系统引导的最后一个阶段,即启动前启动。 作者:bromine链接:https://www.jianshu.com/p/e63...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。Swoft Github: https://githu...

    iflove 评论0 收藏0
  • Swoft 源码剖析 - Swoole和Swoft的那些事(Task投递/定时任务篇)

    摘要:作为定时任务的执行者,通过每唤醒自身一次,然后把执行表遍历一次,挑选当下需要执行的任务,通过投递出去并更新该任务执行表中的状态。 作者:bromine链接:https://www.jianshu.com/p/b44...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。Swoft Github: https://github.com/swoft-clou.....

    vvpvvp 评论0 收藏0
  • Swoft 源码剖析 - 连接池

    摘要:基于扩展实现真正的数据库连接池这种方案中,项目占用的连接数仅仅为。一种是连接暂时不再使用,其占用状态解除,可以从使用者手中交回到空闲队列中这种我们称为连接的归队。源码剖析系列目录 作者:bromine链接:https://www.jianshu.com/p/1a7...來源:简书著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。Swoft Github: https:...

    rozbo 评论0 收藏0

发表评论

0条评论

zzbo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<