什么是注解(Annotation)呢?

注解也叫元数据,用于对代码进行说明,可以对类、接口、字段、方法、参数等进行注解。注解是一种分散式的元数据,与源代码紧密绑定。

注解有什么用途呢?

  • 生成文档,通过代码中标识的元数据生成文档,比如Java使用注解生成的javadoc文档。
  • 编译检查,通过代码中标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码中的元数据进行动态处理,比如生成动态代码。
  • 运行时动态处理,运行时通过代码中的标识的元数据进行动态处理,例如使用反射注入实例。

注解与XML有什么异同点呢?

早期XML是各大框架的青睐者,因为它以松耦合的方式完成了框架中几乎所有的配置,但随着项目越来越大,XML的内容也越来越复杂,维护成本变的越来越高。

于是就有人提出一种标记式高耦合的配置方式“注解”,于是乎方法上可以进行注解,类上可以注解,字段上也可以注解,反正几乎需要配置的地方都可以进行注解。

关于注解和XML两种不同的配置模式,争论了很多年,各有优缺。注解提供了更大的便捷性,易于维护修改,但耦合度高。而XML相对于注解则正好相反。如果为了追求低耦合就需要抛弃高效率,追求高效率必然会遇到耦合。

注解本质上是什么呢?

The common interface extended by all annotation types

注解的本质其实就是继承了Annotation接口的接口,一个注解准确意义上来讲,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。

解析一个类或方法的注解往往有两种方式:

  • 一种是编译期直接扫描
    编译器在对代码编译成为字节码的过程中会检测某个类或方法是否被注解修饰,此时会对注解进行处理。
  • 一种是运行期反射

PHP有注解吗?

注解是指附加在数据或代码上的元数据(metadata),框架可以基于元数据为代码提供各种额外的功能,在Swoft中注解是实现AOP、IoC容器的基础。

如果想象代码是具有生命的个体,那么注解就相当于为代码中某些鲜活个体贴上去一张标签,简单来说注解如同标签。

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

PHP官方对注解的方案并没达成一致,目前PHP没有对注解的官方实现。主流的PHP框架使用的注解都是采用T_DOC_COMMENT类型注释块中的@Tag,来定义自己的注解机制。

Swoft中的注解是如何实现的呢?

Swoft没有重复造轮子,而是选择采用Doctrine的注解引擎。Doctriine的注解方案是基于T_DOC_COMMENT型注释的,Doctrine使用反射获取代码的T_DOC_COMMENT型注释,并将注释中的特定类型@Tag映射到对应注解类。

定义注解

因此,Swoft首先要为每个框架自定义的注解定义注解类。自定义注解时,方法注解和属性注解依赖于类注解。框架只主动扫描类注解,从类注解的Wrapper类中获取应该扫描哪些方法注解和属性注解。

Swoft框架内置的注解类位于vendor/swoft/http-server/src/Bean/Annotation文件夹下,比如在控制器中经常使用使用@Controller注解的定义。

<?php
namespace Swoft\Http\Server\Bean\Annotation;
/**** 控制器自动解析注解路由** @Annotation //声明一个注解类* @Target("CLASS") // 声明注解只能用在class类级别** @uses      Controller* @version   2017年08月22日* @author    stelin <phpcrazy@126.com>* @copyright Copyright 2010-2016 Swoft software* @license   PHP Version 7.x {@link http://www.php.net/license/3_0.txt}*/
class Controller
{/*** @var string 控制器前缀*/private $prefix = '';/*** AutoController constructor.** @param array $values*/public function __construct(array $values){if (isset($values['value'])) {$this->prefix = $values['value'];}if (isset($values['prefix'])) {$this->prefix = $values['prefix'];}}/*** 获取controller前缀** @return string*/public function getPrefix(): string{return $this->prefix;}
}
  • @Annotation 用于声明当前类是一个注解类
  • @Target("CLASS") 用于声明当前类只能用于class类级别的注解中
  • @var是PHPDoc标准中的tag,定义了属性的类型,Doctrine会根据类型额外对注解参数进行检查。
  • @param Doctrine注解使用的参数

如果注解类提供了构造器__constructor,Doctrine会调用并在此处对注解类对象的私有private属性进行赋值。

自定义注解类

想要定义一个注解,首先需要新建注解类,例如:

$ vim app/Module/Test/Annotations/Test.php
<?php
namespace App\Module\Test\Annotations;/*** 注解* @Annotation* @Target("ALL")* @package Module/Test/Annotations
*/
class Test
{/*** @var string $name*/private $name = "";public function __construct(array $values){if(isset($values["name"])){$this->name = $values["name"];}}/*** @return string*/public function getName():string{return $this->name;}/*** @param string $name*/public function setName(string $name){$this->name = $name;}
}

自定义注解类时需要注意的的是

  • 类注解要添加@Annotation用于声明这是一个注解类
  • 类名不需要添加Annotation后缀
  • 类注解@Target()Doctrine\Common\Annotations\Annotation\Target.php,Target函数的参数可选择ALL | CLASS | METHOD | PROPERTY | ANNOTATION,表示注解使用的级别。

自定义注解类后就可以在控制器中使用自定义的注解标签, 例如在IndexController.php控制器添加自定义标签。

use App\Module\Test\Annotations\Test;
/*** Class IndexController* @Controller()* @Test(name='index')*/
class IndexController
{}

注意这里的@Test(name='index')相当于new Test([name=>"index"])

注解解析类Parser

注解只是配置的另种展现形式,任何逻辑都不要在注解类中处理。

<?php
namespace App\Module\Test\Parser;use Swoft\Bean\Parser\AbstractParser;
use App\Module\Test\Collector\TestCollector;class TestParser extends AbstractParser
{public function parser(string $className, $objectAnnotation = null, string $propertyName = "", string $methodName = "", $propertyValua = null){TestCollector::collect($className, $objectAnnotation, $propertyName, $methodName,  $propertyName);}
}

注意parser方法参数的含义
parser(string $className, $objectAnnotation = null, string $propertyName = "", string $methodName = "", $propertyValua = null)

  • string $className 当前注解所在的类名
  • $objectAnnotation 当前注解实例化的注解对象,
  • $propertyName当前注解所在的属性,如果是属性注解。
  • $methodName 当前注解所在的方法名,如果是方法注解。
  • $propertyValue当前注解所在的属性,如果是属性注解。

注解解析类Parser只需要做一件事情,就是把注解类存入到注解收集器类。因此这里也不要处理逻辑,因为此刻程序还处于初始化阶段,没有请求数据。

注解收集类Collector

  • 注解收集类Collector只是存取$objectAnnotation注解实例,方便后面使用。
  • 注解收集类Collector被注解解析类Parser调用
<?php
namespace App\Module\Test\Collector;
use Swoft\Bean\CollectorInterface;class TestCollector implements CollectorInterface
{private static $test = [];public static function collect(string $className, $objectAnnotation=null, string $propertyName="", string $methodName="", $propertyValue=null){self::$test[$className][$methodName] = $objectAnnotation;}public static function getCollector(){return self::$test;}
}

注解封装类Wrapper

当类注解被实例化时会触发注解封装类{注解标签名}Wrapper,注解封装类Wrapper来决定是否触发注解解析类Parser

<?php
namespace App\Module\Test\Wrapper;
use App\Module\Test\Annotations\Test;
use Swoft\Bean\Wrapper\AbstractWrapper;
/*注解封装类*/
class TestWrapper extends AbstractWrapper
{/*** @var array 解析哪些类级注解*/protected $classAnnotations = [];/*** @var array 解析哪些属性级注解*/protected $propertyAnnotations = [];/*** @var array 解析哪些方法级注解*/protected $methodAnnotations = [Test::class];/*** 是否解析类注解* @param array $annotations* @return bool*/public function isParseClassAnnotations(array $annotations):bool{return false;}/*** 是否解析属性注解* @param array $annotations* @return bool*/public function isParsePropertyAnnotations(array $annotations):bool{return false;}/*** 是否解析方法注解* @param array $annotations* @return bool*/public function isParseMethodAnnotations(array $annotations):bool{return false;}
}

加载注解

注解类加载器的注册是在框架的启动bootstrap阶段进行的,Swoft会扫描所有PHP源码文件并获取和解析注解信息。

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

$ /vendor/swoft/framework/src/Bean/Resource/AnnotationResource.php
<?phpnamespace Swoft\Bean\Resource;use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Swoft\Bean\Wrapper\WrapperInterface;
use Swoft\Helper\ComposerHelper;/*** Annotation resource*/
abstract class AnnotationResource extends AbstractResource
{/*** 注册加载器和扫描PHP文件** @return array*/protected function registerLoaderAndScanBean(){$phpClass = [];foreach ($this->scanNamespaces as $namespace => $dir) {AnnotationRegistry::registerLoader(function ($class) {if (class_exists($class) || interface_exists($class)) {return true;}return false;});$scanClass = $this->scanPhpFile($dir, $namespace);$phpClass  = array_merge($phpClass, $scanClass);}return array_unique($phpClass);}
}

使用注解

使用Doctrine获取注解对象

解析注解

当扫描源码目录并获取PHP类后,Swoft会遍历类列表并加载类,获取类级别、方法级别、属性级别的所有注解对象。再将结果存放到AnnontationResource类的$annotations成员属性中。

$ /vendor/swoft/framework/src/Bean/Resource/AnnotationResource.php
<?phpnamespace Swoft\Bean\Resource;use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Swoft\Bean\Wrapper\WrapperInterface;
use Swoft\Helper\ComposerHelper;/*** Annotation resource*/
abstract class AnnotationResource extends AbstractResource
{/*** 解析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);$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数组中。
/*** 类注解封装** @param string $className* @param array  $annotation* @param array  $classAnnotations*/
private function parseClassAnnotations(string $className, array $annotation, array $classAnnotations)
{foreach ($classAnnotations as $classAnnotation) {$annotationClassName = get_class($classAnnotation);$classNameTmp        = str_replace('\\', '/', $annotationClassName);$classFileName       = basename($classNameTmp);$beanNamespaceTmp    = dirname($classNameTmp, 2);$beanNamespace       = str_replace('/', '\\', $beanNamespaceTmp);$annotationWrapperClassName = "{$beanNamespace}\\Wrapper\\{$classFileName}Wrapper";if (!class_exists($annotationWrapperClassName)) {continue;}/* @var WrapperInterface $wrapper */$wrapper = new $annotationWrapperClassName($this);// wrapper extendforeach ($this->componentNamespaces as $componentNamespace) {$annotationWrapperExtendClassName = "{$componentNamespace}\\Bean\\Wrapper\\Extend\\{$classFileName}Extend";if (!class_exists($annotationWrapperExtendClassName)) {continue;}$extend = new $annotationWrapperExtendClassName();$wrapper->addExtends($extend);}$objectDefinitionAry = $wrapper->doWrapper($className, $annotation);if ($objectDefinitionAry != null) {list($beanName, $objectDefinition) = $objectDefinitionAry;$this->definitions[$beanName] = $objectDefinition;}}
}
 $objectDefinitionAry = $wrapper->doWrapper($className, $annotation);
  • 在注解解析时Parser会调用相关的Collector集合搜集所需的信息,比如进行事件注册。

由于Swoft框架执行前必须完整的获取各种注解到收集器Collector并生成Bean定义集合,所以Swoft是不进行懒加载lazyload的。

使用注解

Swoft框架启动时会扫描PHP源码,将Bean的定义信息存放到ObjectDefinition数组中,将注解信息存放到各个Collector中。因此在框架的启动Bootstrap阶段,可以从BootstrapCollector启动收集器中直接获得@Bootstrap型的Bean,实例化并Bean执行。

PHP Annotations

PHP Annontations是PHPStorm支持的注解插件

  • http://plugins.jetbrains.com/plugin/7320-php-annotations
PHP Annotations

Swoft Annotation 注解相关推荐

  1. java 中的 Annotation 注解学习笔记

    java 中的 Annotation 注解 什么是注解 元注解 @Target @Retention @Document @Inherited 自定义注解 什么是注解 Annotation 是从JDK ...

  2. 声明属性Hibernate的Annotation注解

    工作之余抽点时间出来写写博文,希望对新接触的朋友有帮助.今天在这里和大家一起学习一下声明属性 当项目变得比较大的时候,如何还应用hbm.xml文件来配置Hibernate实体就会变得比较复杂.这里Hi ...

  3. Java反射学习总结五(Annotation(注解)-基础篇)

    Annotation(注解)简单介绍: 注解大家印象最深刻的可能就是JUnit做单元測试,和各种框架里的使用了. 本文主要简介一下注解的用法,下篇文章再深入的研究. annotation并不直接影响代 ...

  4. hibernate annotation注解方式来处理映射关系

    2019独角兽企业重金招聘Python工程师标准>>> 在hibernate中,通常配置对象关系映射关系有两种,一种是基于xml的方式,另一种是基于annotation的注解方式,熟 ...

  5. java bean 工厂模式_通过annotation(注解)实现BeanFactory工厂模式(三)

    工厂模式是大家熟知的一种设计模式,在spring BeanFactory将这模式运用自如. 前面讲过如果通过xml配置的方式实现,今天我们来讲讲如何通过注解的方式实现工厂模式. 主要思路 扫描clas ...

  6. java 注解 对象_Java基础-学习使用Annotation注解对象

    Java基础-学习使用Annotation注解对象 注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某 个时刻非常方便地使用这些数据 1-1:基本语法 Java S ...

  7. java 数据校验框架_自己写的基于java Annotation(注解)的数据校验框架

    JavaEE6中提供了基于java Annotation(注解)的Bean校验框架,Hibernate也有类似的基于Annotation的数据校验功能,我在工作中,产品也经常需要使 用数据校验,为了方 ...

  8. 【修真院Java小课堂】Annotation注解

    标题: [修真院Java小课堂]Annotation注解 开场语: 大家好,我是IT修真院西安分院第三期学员,一枚正直纯洁善良的JAVA程序员,今天给大家分享一下,修真院官网JAVA任务七,深度思考中 ...

  9. Annotation,注解

    1.注解,或者叫做 注释,英语单词:annotation 注解的作用是什么呢? 2.注解annotation是一种引用数据类型.编译之后也是生成xxx.class文件 3.怎么自定义注解呢?语法格式? ...

最新文章

  1. 到底为什么你我都要了解社会工程学
  2. JQuery-FullCalendar 多数据源实现日程展示
  3. 【Python】利用Conda尝鲜Python 3.10
  4. 数据结构与算法--有序数组中找出和为s的两个数字
  5. junit5和junit4_JUnit 5 –设置
  6. 外连接就是允许不满足条件的字段查询出来
  7. fedora操作系统优缺点_不同类型的操作系统的优缺点
  8. 后端开发工具:反编译工具、VS插件、.NET Framework源码地址
  9. html点线面制作,利用HTML5绘制点线面组成的3D图形的示例_html5教程技巧
  10. signature=0727ee8cc38ba70036807ebbc0b018d6,NMSSM+
  11. 《计算机组成与设计(硬件/软件接口)》读书笔记
  12. 继电保护计算机化,浅谈继电保护的未来发展趋势
  13. 数据库编程之ODBC编程
  14. BlueCoat ProxySG配置FTP代理
  15. dd命令创建一个大文件
  16. 小米手机 开发app python_一篇文章教会你用Python多线程获取小米应用商店App
  17. linux mysql dengl_linux环境搭建(四)--MYSQL
  18. ES6----promise方法解决回调地狱问题
  19. 【无人机】基于Matlab实现无人机轨迹规划目标跟踪附论文报告和代码
  20. cpu上干硅脂怎么清理_被骗很久!这才是给CPU涂硅脂正确方法...

热门文章

  1. UDP和TCP(Java版)
  2. 连续激光电源市场现状及未来发展趋势分析
  3. python read csv多个分隔符_pandas read_csv()用于多个分隔符
  4. 软路由初步尝试-U盘运行LEDE
  5. 《思考的技术》笔记一
  6. 输入一个Email地址,然后使用正则表达式验证该Email地址是否正确。
  7. eyeos 云计算操作系统
  8. SSL/TLS攻击介绍--重协商漏洞攻击
  9. osgEarth的Rex引擎原理分析(九十)如何设置高度单位(m、km等)
  10. synergy一个鼠标控制多个电脑,synergy怎么用?synergy配置教程