从bin/swoft开始,阅读Swoft框架源码(六--一)--BeanProcessor之注解解析

码农天地 -
从bin/swoft开始,阅读Swoft框架源码(六--一)--BeanProcessor之注解解析

Bean又是一个Swoft的核心概念.
BeanProcessor就是能在Swoft中使用Bean的基础.

由于bean处理器全部由swoft原生实现,所以代码量较大,逻辑也比较复杂,所以bean处理器这一模块将分为三个子章节(注解对象解析、定义对象解析、初始化bean)来进行分析.

这里先介绍一个概念(这是作者自己的理解,并非官方的解释):定义对象

首先,先看bean的概念:

1.bean对象实际上就是一个对象模版,不同的bean类型代表了如何根据这个模版对象返回对应的对象.
2.bean总共有4种类型: singleton、prototype、request和session.
    singleton类型直接返回这个模版对象本身.
    prototype是将这个模版对象作为新对象的原型,返回模版对象的克隆对象.
    request和session是根据模版对象克隆出新的对象,然后在某一种条件下保持单例,直至条件不满足新对象被销毁.

然后基于模版这个概念,得出定义对象的概念:

用来定义返回对象类型、结构和内容的模版.

依旧先从入口方法handle入手:

public function handle(): bool
{
     if (!$this->application->beforeBean()) {
        return false;
     }
     
     // 实例化handler
     $handler = new BeanHandler();
     
     // 获取所有定义的bean配置
     $definitions = $this->getDefinitions();
     
     // 获取在注解处理器中parseOneClassAnnotation方法注册的parser对象
     $parsers = AnnotationRegister::getParsers();
     
     // 获取注解处理器注册的注解对象
     $annotations = AnnotationRegister::getAnnotations();
     // 将获取到的bean配置保存到bean容器中
     // 这里的BeanFactory实际上是将内容又转交给了Container
     BeanFactory::addDefinitions($definitions);
     // 将获取的注解对象转交给bean容器
     BeanFactory::addAnnotations($annotations);
     // 将获取的parser对象转交给bean容器
     BeanFactory::addParsers($parsers);
     // 将前面实例化的handler设置为bean容器的handler
     BeanFactory::setHandler($handler);
     // bean容器初始化
     // 后附初始化代码
     BeanFactory::init();
     
     $stats = BeanFactory::getStats();
     CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats));
     /* @var Config $config */
     $config = BeanFactory::getBean('config');
     CLog::info('Config path is %s', $config->getPath());
     if ($configEnv = $config->getEnv()) {
        CLog::info('Config env=%s', $configEnv);
     } else {
        CLog::info('Config env is not setting');
     }
     return $this->application->afterBean();
}

getDefinitions实现:

private function getDefinitions(): array
{
     // Core beans
     $definitions = [];
     // 此处获取到的autoLoaders就是上一章注解处理器注册的autoLoader
     $autoLoaders = AnnotationRegister::getAutoLoaders();
     
     // 获取失效的autoLoaders
     // get disabled loaders by application
     $disabledLoaders = $this->application->getDisabledAutoLoaders();
     
     // 遍历autoLoaders
     // 获取各autoLoader中beans方法注册的bean配置
     // 保存在definitions数组中
     foreach ($autoLoaders as $autoLoader) {
         // 如果不是DefinitionInterface的实现,跳过
         if (!$autoLoader instanceof DefinitionInterface) {
            continue;
         }
         // 获取autoLoader的类名,包含命名空间
         $loaderClass = get_class($autoLoader);
         // If the component is disabled by app.
         // 如果是被app定义的无效autoLoader,则打印并跳过
         if (isset($disabledLoaders[$loaderClass])) {
             CLog::info('Auto loader(%s) is <cyan>DISABLED</cyan>, skip handle it', $loaderClass);
             continue; 
         }
         // 如果当前autoLoader本身不是enable,则打印并跳过
         // If the component is disabled by self.
         if (!$autoLoader->isEnable()) {
             CLog::info('Auto loader(%s) is <cyan>DISABLED</cyan>, skip handle it', $loaderClass);
             continue; 
         }
         // 获取注册的bean配置并与返回值合并
         $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());
     }
     
     // 获取应用的bean文件
     // Application bean definitions
     $beanFile = alias($this->application->getBeanFile());
     if (!file_exists($beanFile)) {
        throw new InvalidArgumentException(sprintf('The bean config file of %s is not exist!', $beanFile));
     }
     // 获取应用bean文件中定义的bean配置
     /** @noinspection PhpIncludeInspection */
     $beanDefinitions = require $beanFile;
     
     // 合并两种途径中获取的bean配置,然后返回给调用者
     return ArrayHelper::merge($definitions, $beanDefinitions);
}

bean容器初始化

public function init(): void
{
     // Parse annotations
     // 解析注解对象,作用是将前面注解处理器获取的注解对象
     // 分门别类的保存到Container容器内
     $this->parseAnnotations();
     
     // Parse definitions
     $this->parseDefinitions();
     // Init beans
     $this->initializeBeans();
}

本章节主要讲解注解对象的解析

注解解析的入口方法parseAnnotations:

private function parseAnnotations(): void
{
     // 实例化注解对象解析器
     // 实例化只是单纯的将传入的属性进行保存
     // 未做其它操作
     $annotationParser = new AnnotationObjParser(
         $this->definitions,
         $this->objectDefinitions,
         $this->classNames,
         $this->aliases
     );
     
     // 解析注解
     // 后附实现代码
     $annotationData = $annotationParser->parseAnnotations($this->annotations, $this->parsers);
     
     // 将解析结果保存在容器内
     [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
}

swoft中关于annotations数组结构的注释:

/**
 * @var array
 * * @example
 * [
 *    'loadNamespace' => [ //注解的命名空间
 *        'className' => [ //调用注解的类的类名
 *             'annotation' => [ //调用的类注解对象集合
 *                  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(), //调用类方法的反射对象
 *                  ] 
 *            ] 
 *        ] 
 *    ] 
 * ] 
 */

AnnotationObjParser的parseAnnotations实现:

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];
}

每一个类的注解解析:

private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
{
     // Check class annotation tag
     // 如果被解析的类没有注解对象,则抛出异常
     if (!isset($classOneAnnotations['annotation'])) {
         throw new AnnotationException(sprintf(
             'Property or method(%s) with `@xxx` must be define class annotation',
             $className
         ));
     }
     
     // 解析类注解
     // Parse class annotations
     // 获取类对应的所有注解对象
     $classAnnotations = $classOneAnnotations['annotation'];
     // 获取类对应的反射对象
     $reflectionClass = $classOneAnnotations['reflection'];
     // 创建参数数组,包含类名、类反射对象、类所有注解对象
     $classAry = [
         $className,
         $reflectionClass,
         $classAnnotations
     ];
     // 解析类注解(后面还会依次解析属性和方法注解),返回定义对象
     $objectDefinition = $this->parseClassAnnotations($classAry);
     
     // 解析属性注解
     // Parse property annotations
     // 保存属性注入对象的数组
     $propertyInjects = [];
     // 获取当前类下的所有属性
     $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
     // 遍历属性数组,得到属性名和对应属性注解集合
     foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
         // 获取属性的所有注解对象
         $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
         // 解析属性注解
         $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
         // 保存获取的属性注入对象
         if ($propertyInject) {
            $propertyInjects[$propertyName] = $propertyInject;
         }
     }
     // Parse method annotations
     // 保存方法注入对象的数组
     $methodInjects = [];
     // 获取方法注解对象
     $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
     // 遍历方法注解对象
     foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
         // 获取注解对象数组
         $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];
         // 解析方法注解对象
         $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
         // 不会进入,因为此值永远为null
         if ($methodInject) {
            $methodInjects[$methodName] = $methodInject;
         }
     }
     // 如果未获取到类的定义对象,则直接返回
     if (!$objectDefinition) {
        return;
     }
     // 如果属性注入对象不为空,则将属性注入对象
     // 保存到当前的定义模版对象上
     if (!empty($propertyInjects)) {
        $objectDefinition->setPropertyInjections($propertyInjects);
     }
     // 保存方法注入对象,这里永远为空
     if (!empty($methodInjects)) {
        $objectDefinition->setMethodInjections($methodInjects);
     }
     
     // Object definition and class name
     // 获取定义对象的名称
     $name = $objectDefinition->getName();
     // 获取定义对象的别名
     $aliase = $objectDefinition->getAlias();
     // 获取当前对象已保存的类名与定义对象名的映射
     $classNames = $this->classNames[$className] ?? [];
     // 添加类名与定义对象名的映射
     $classNames[] = $name;
     // 去重并设置回当前对象
     $this->classNames[$className]   = array_unique($classNames);
     // 按名称保存定义对象
     $this->objectDefinitions[$name] = $objectDefinition;
     // 如果有别名,设置别名与定义对象名称的映射
     if (!empty($aliase)) {
        $this->aliases[$aliase] = $name;
     }
}

解析类注解parseClassAnnotations的实现:

private function parseClassAnnotations(array $classAry): ?ObjectDefinition
{
     // 获取传递进来的类注解对象集合
     [, , $classAnnotations] = $classAry;
     // 返回值
     $objectDefinition = null;
     //遍历类的注解对象集合,找到每一个注解对象
     foreach ($classAnnotations as $annotation) {
         // 获取注解对象的类名
         $annotationClass = get_class($annotation);
         // 如果没有找到这个类的解析器类名,则跳过
         if (!isset($this->parsers[$annotationClass])) {
            continue;
         }
         // 获取这个类的解析器类名
         $parserClassName = $this->parsers[$annotationClass];
         // 通过解析器类名获取注解解析器
         // 方法内是new传入的解析器类名.
         // 将参数中的类名、类反射对象、类注解集合作为构造参数传递给被new的解析器
         // 然后返回new出来的解析器实例
         $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
         // 解析类注解
         // ParserInterface中关于parse方法的描述如下:
         // @param int    $type             Class or Method or Property
         // @param object $annotationObject Annotation object
         // @return array
         // Return empty array is nothing to do!
         // When class type return [$beanName, $className, $scope, $alias] is to inject bean
         // When property type return [$propertyValue, $isRef] is to reference value
         // 返回值部分翻译过来就是:
         // 如果返回空数组,系统就不会做更多的操作
         // 如果返回数组为[$beanName, $className, $scope, $alias]
         // 则表示注入bean
         // 如果返回[$propertyValue, $isRef]则表示对value的引用
         $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);
         // 所以,如果返回为空,则跳过
         if (empty($data)) {
            continue;
         }
         // 类注解长度不为4,就是有返回值,但是不是注入bean的情况,则抛出异常
         if (count($data) !== 4) {
            throw new InvalidArgumentException(sprintf('%s annotation parse must be 4 size', $annotationClass));
         }
         
         // bean就相当于是模版对象,不同的bean类型代表了如何根据这个模版对象返回对应的对象
         // singleton类型直接返回这个模版对象本身
         // prototype是将这个模版对象作为新对象的原型,返回模版对象的克隆对象
         // request和session是根据模版对象克隆出新的对象,然后在某一种条件下保持单例,直至条件不满足新对象被销毁
         // 这个方法后面的代码就是创建这个模版对象
         // swoft里面给这个模版对象取名翻译过来叫做:定义对象
         
         // 将返回值拆分为对应的变量
         // $name表示构建的定义对象的名称
         // $className表示构建的定义对象的类名
         // $scope表示构建的定义对象的类型
         // $alias表示构建的定义对象的别名
         [$name, $className, $scope, $alias] = $data;
         
         // 如果没设置名称,则将类名作为默认名称
         $name = empty($name) ? $className : $name;
         
         // 如果连类名都是空的,则抛出异常
         if (empty($className)) {
            throw new InvalidArgumentException(sprintf('%s with class name can not be empty', $annotationClass));
         }
         
         // 实例化定义对象
         // Multiple coverage
         // 动态覆盖,后遍历到的实例会覆盖前面构造的实例
         // 至于swoft有没有对这个遍历顺序做过操作,以及为何会这样动态覆盖,后面再去研究.
         $objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);
     }
     // 返回定义对象
     return $objectDefinition;
}

定义对象的结构大致如下:

object(Swoft\Bean\Definition\ObjectDefinition)#4377 (7) {
  ["name":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  string(24) "App\WebSocket\TestModule"
  ["className":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  string(24) "App\WebSocket\TestModule"
  ["scope":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  string(9) "singleton"
  ["alias":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  string(0) ""
  ["constructorInjection":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  NULL
  ["propertyInjections":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  array(0) {
  }
  ["methodInjections":"Swoft\Bean\Definition\ObjectDefinition":private]=>
  array(0) {
  }
}

解析属性注解的实现parsePropertyAnnotations:

private function parsePropertyAnnotations(
 array $classAry,
 string $propertyName,
 array $propertyAnnotations
): ?PropertyInjection {
     // 属性注入对象
     $propertyInjection = null;
     // 遍历属性注解对象
     foreach ($propertyAnnotations as $propertyAnnotation) {
         // 获取属性注解对象的类名
         $annotationClass = get_class($propertyAnnotation);
         // 如果没有对应的解析器类名,则跳过
         if (!isset($this->parsers[$annotationClass])) {
            continue;
         }
         // 获取解析器类名
         $parserClassName = $this->parsers[$annotationClass];
         // 和类注解解析器获取一样,都是调用同一个方法
         // 返回的是解析器实例
         $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
         // 给解析器设置属性名称
         $annotationParser->setPropertyName($propertyName);
         // 调用解析器的解析方法
         $data = $annotationParser->parse(Parser::TYPE_PROPERTY, $propertyAnnotation);
         // 如果返回值是空数组,则无需处理,跳过
         if (empty($data)) {
            continue;
         }
         // 如果返回值不是[$propertyValue, $isRef]这种结构
         // 则抛出异常
         if (count($data) !== 2) {
            throw new InvalidArgumentException('Return array with property annotation parse must be 2 size');
         }
         // 获取当前属性解析器中保存的模版定义对象
         $definitions = $annotationParser->getDefinitions();
         // 如果有值
         if ($definitions) {
            // 则合并到当前对象的定义对象数组中,处理完成后整个数组会被返回
            $this->definitions = $this->mergeDefinitions($this->definitions, $definitions);
         }
         // Multiple coverage
         // 又是动态覆盖
         [$propertyValue, $isRef] = $data;
         // 这里创建的是属性注入对象,与类注解解析方法创建的
         // 定义对象是不同的
         $propertyInjection = new PropertyInjection($propertyName, $propertyValue, $isRef);
     }
     // 返回属性注入对象
     return $propertyInjection;
}

属性注入对象的结构大致如下:

object(Swoft\Bean\Definition\PropertyInjection)#4398 (3) {
  ["propertyName":"Swoft\Bean\Definition\PropertyInjection":private]=>
  string(5) "logic"
  ["value":"Swoft\Bean\Definition\PropertyInjection":private]=>
  string(28) "App\Model\Logic\MonitorLogic"
  ["isRef":"Swoft\Bean\Definition\PropertyInjection":private]=>
  bool(true)
}

解析方法注解parseMethodAnnotations:

private function parseMethodAnnotations(
 array $classAry,
 string $methodName,
 array $methodAnnotations
): ?MethodInjection {
     // 需要返回的方法注入对象 此方法未对这个变量再次赋值
     // 所以返回的这个值永远是null
     $methodInject = null;
     // 遍历方法注解对象
     foreach ($methodAnnotations as $methodAnnotation) {
         // 获取方法注解对象的类名
         $annotationClass = get_class($methodAnnotation);
         
         // 如果不存在对应解析器类名,则跳过
         if (!isset($this->parsers[$annotationClass])) {
            continue;
         }
         // 获取解析器类名
         $parserClassName = $this->parsers[$annotationClass];
         // 获取解析器实例
         $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
         // 设置被解析的方法名
         $annotationParser->setMethodName($methodName);
         // 调用解析器的解析方法
         $data = $annotationParser->parse(Parser::TYPE_METHOD, $methodAnnotation);
         // 如果返回空数组则跳过
         if (empty($data)) {
            continue;
         }
         // 如果解析器有定义对象,则与当前对象的定义对象数组合并
         $definitions = $annotationParser->getDefinitions();
         if ($definitions) {
            $this->definitions = $this->mergeDefinitions($this->definitions, $definitions);
         }
     }
     // 此处返回的是null
     return $methodInject;
}

总结:

1.注解对象解析方法流程梳理:
    (1):遍历所有注解对象,得到命名空间下的所有类名与包含类注解对象(annotation)、调用注解的类的反射对象(reflection)、属性注解对象(properties如果有的话)和方法注解对象(metheds如果有的话)结构的数组.
    (2):遍历第一步得到的每个命名空间下的数组,得到每个类的类名与对应的数据结构.
    (3):拿到数据结构后,先得到对应的解析器,先调用解析器parse方法,然后解析出(new出)类的定义对象.
    (4):接着遍历结构中的属性注解对象,获取到每个属性注解的解析器,调用parse方法、合并解析器中的定义对象到全局定义对象数组,解析出(new出)属性的注入对象.
    (5):然后遍历结构中的方法注解对象,获取到每个方法注解对象的解析器,调用parse方法、合并解析器中的定义对象到全局定义对象数组.
    (6):保存属性注入对象到定义对象.
    (7):保存方法注入对象到定义对象(由于没有返回方法注入对象,此处不会执行).
    (8):全局保存定义对象名称、定义对象以及别名和定义对象名称的映射关系.
2.注解对象解析方法功能:
    创建注解类的定义对象,给定义对象添加属性和方法注入对象,按名称保存注解对象,保存调用类的类名与定义对象名的映射关系,保存别名与定义对象名的映射关系.

附:
bean配置的数据结构:

/**
 * All definitions * * @var array
 * * @example
 * [
 *     'name' => [ 
 *         'class' => 'className', 
 *         [ 
 *             'construnctArg', 
 *             '${ref.name}', // config params 
 *             '${beanName}', // object 
 *         ], 
 *         'propertyValue', 
 *         '${ref.name}', 
 *         '${beanName}', 
 *         '__option' => [ 
 *              'scope' => '...', 
 *              'alias' => '...', 
 *         ] 
 *     ] 
 * ] 
 */

保存定义对象的数组结构:

/**
 * Bean definitions * * @var ObjectDefinition[]
 * * @example
 * [
 *     'beanName' => new ObjectDefinition, 
 *     'beanName' => new ObjectDefinition, 
 *     'beanName' => new ObjectDefinition 
 * ] 
 */

保存调用类与被调用的定义对象名称的数据结构:

/**
 * Class all bean names (many instances) * * @var array
 * 
 * @example
 * [
 *     'className' => [ //调用类名
 *         'beanName', // 定义对象名
 *         'beanName', 
 *         'beanName', 
 *     ] 
 * ] 
 */

保存定义对象别名的数据结构:

/**
 * All alias * * @var array
 * 
 * @example
 * [
 *     'alias' => 'beanName', 
 *     'alias' => 'beanName', 
 *     'alias' => 'beanName' 
 * ] 
 */
特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

php介绍

PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。

Tags 标签

phpswooleswoft

扩展阅读

加个好友,技术交流

1628738909466805.jpg