Annotation 注解机制
概述
说到注解,这里不得不提到注释。写过程序的人都知道,注释,是用来给类、接口、方法、变量等做解释用的一种机制,用来给阅读程序的人提供提醒备忘的作用。那么注解又是用来干什么的呢。注解的作用,是在保持原注释格式的基础之上,实现了一种更高级的用法或者说作用。其具体作用如下:
-
功能的复用
类似于函数,就是为了将部分重复的逻辑代码进行复用,以减少代码的冗余,提供程序修改的便利。注解,也是,同样的注解,提供的同样的功能作用
-
提供元数据
注解运行的结果,主要是给业务系统(框架)提供必需的元数据,以便框架能够正常运行
-
解耦合
注解在解析过程中,可以提供其他的一些组件所必须的数据,这样不用提供硬性编码,通过注解功能,将不同的组件的依赖关系进行解耦合。
运行阶段
注解的解析主要出于框架的启动阶段,启动阶段不同于运行阶段,虽然也执行代码,但是启动过程中,整个业务系统所需要的必须组件并未初始化完成,还不足以使业务系统能够正常运行,处理业务请求。
-
程序启动阶段
获取系统元数据,根据元数据的依赖关系,组件依赖关系,依次初始化业务系统运行所需要的必须组件。
-
程序运行阶段
提供正常的业务请求处理能力。
-
程序结束阶段
停止接收新的业务请求,并处理完当前系统中已经接收的所有请求。处理完毕后,依次回收(释放)系统组件所占用的资源。
注解的解析,就处在程序启动阶段,启动过程中,注解核心组件先进行实例化,然后对系统中的所有代码进行扫描,将代码存在的所有注解信息一一收集,并且依次对注解进行解析。解析过程中,有些注解可能会注册一些其他组件依赖的一些关键数据(如:@RequestMapping 在解析过程中提供了路由信息)
;有些注解会收集部分数据,并且形成一个对象的实例(如:@Controller 注解会生成控制器的单例)存储到(IOC支持的)容器中
。以上是注解的一般用法。
注解参数
注解同样可以使用参数,使有限的逻辑能够最大化的复用,效果也类似带参数的函数。
注解作用域
与原注释功能基本保持一致,一般有:
- 类注解(针对类的注解)
- 属性注解(针对类属性的注解)
- 方法注解(针对方法的注解)
源码解析
注解组成
- 注解对象
- 注解解析器对象
- 注解解析器核心组件
src/Definition/Parser/AnnotationObjParser.php
注解对象
需要包含三个注解:
- @Annotation:表示此类是一个注解对象
- @Target:可以跟多个,表示注解可以标注的位置为多个
- CLASS:表示类注解
- METHOD: 表示成员方法注解
- PROPERTY:表示属性注解
- FUNCTION:暂无说明
- ANNOTATION:暂无说明
- @Attributes:表示此注解对象中含有的属性,可以同时有多个
- @Attribute:表示注解对象的一个属性
- name:属性名
- type:属性的类型
- required:是否必须
- @Attribute:表示注解对象的一个属性
Parser 与核心解析器源码分析
所有的注解解析器,必须通过类注解:@AnnotationParser(Object::class) 来标识,说明此解析器用来解析哪个注解对象。
解析器必须继承 \Swoft\Annotation\Annotation\Parser\Parser
父类,并且实现 parse
接口。parse 方法中,可以用来给其他的组件提供数据或者其他操作,parse 的返回值也是一些元数据,不同类型的注解返回的数据的要求也不同。
类注解解析器
要求可以返回一个四个元素的数组,如果返回了这个,表示被此注解标识的类,会生成一个自定义的 Bean,并且存储到(IOC)容器中。
php
$data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);
if (empty($data)) {
continue;
}
if (count($data) !== 4) {
throw new InvalidArgumentException(sprintf('%s annotation parse must be 4 size', $annotationClass));
}
// 此处为注解解析器返回的数据
[$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
$objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);
属性注解解析器
要求可以返回一个两个元素的数组,如果返回表示有数据要注入被标注的属性中。
- 属性值(一般为两种)
- 引用数据,可以为bean实例,或者配置信息,如:
.config.key
或beanName
- 固定值,属性值为数据本身,如:一个固定字符串
- 引用数据,可以为bean实例,或者配置信息,如:
- 是否是引用数据(布尔类型)
- true: 表示是引用数据,会去查找配置,或者查找bean实例
- false:表示是一个固定值
php
$data = $annotationParser->parse(Parser::TYPE_PROPERTY, $propertyAnnotation);
if (empty($data)) {
continue;
}
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);
方法解析器
要求不返回数据,当然如果有数据返回,并且 definitions 有数据,则会进一步解析关联的 Bean 的定义,表示此方法的注解,可以生成关联的 bean 实体。如果只提供 bean 名称,则可以给 bean 注入相关数据。
需要注意的是,swoft2 中同一个注解解析器的 definitions ,在每次解析后会通过 merge 进行合并,这就导致 bean 定义中的同一个属性,会被反复覆盖,只保留最后一次的结果。导致无法根据注解的方法,反复给 bean 的定义注入不同的数据。
由于注解解析阶段 bean 一般都没有被实例化,除了通过 bean 定义的方式进行数据注入,还可以仿照框架中各种注册器的方法,就是注解标签绑定的相关元数据,先放入某个目标的 static 静态属性中(不用实例化),后续可以让 bean 直接读取,或者让其在某个(已经有bean实例的)阶段把 static 属性中的数据在转移到目标 bean 中即可,当然,如果嫌费事,直接用 static 属性中的数据也没有问题。
注意:注解中一般不用 BeanFactory 获取 Bean 实体,因为此阶段,Bean 还未进行实例化。
php
// 返回的数据没有用到
$data = $annotationParser->parse(Parser::TYPE_METHOD, $methodAnnotation);
// 如果有数据返回会跳过此处
if (empty($data)) {
continue;
}
// 如果有更多的定义数据数据会进一步解析
$definitions = $annotationParser->getDefinitions();
if ($definitions) {
$this->definitions = $this->mergeDefinitions($this->definitions, $definitions);
}
Swoft2 框架精华教程:Controller 组件解析,使用说明