注解是Swoft的特色之一,Swoft项目中几乎所有的业务代码中都离不开注解.
AnnotationProcessor处理器就是Swoft能在业务中使用组件的核心依赖.
通过注解处理器处理后,会在注解处理器中保存一个这一的数据结构:
/**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(), //调用类方法的反射对象
* ]
* ]
* ]
* ]
* ]
*/
先看AnnotationProcessor的入口方法:
public function handle(): bool
{
// 注解加载前回调
// 系统未做处理 直接返回的true
if (!$this->application->beforeAnnotation()) {
CLog::warning('Stop annotation processor by beforeAnnotation return false');
return false;
}
// 获取当前应用实例,这个实例是在应用初始化处理器时传递给
// 每个处理器的
$app = $this->application;
// 找到AutoLoader类.解析和采集注解.
// Find AutoLoader classes. Parse and collect annotations.
AnnotationRegister::load([
'inPhar' => IN_PHAR,
'basePath' => $app->getBasePath(),
'notifyHandler' => [$this, 'notifyHandle'],
// TODO force load framework components: bean, error, event, aop
'disabledAutoLoaders' => $app->getDisabledAutoLoaders(),
'excludedPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
]);
//
$stats = AnnotationRegister::getClassStats();
// 控制台输出注解的采集和加载数据
CLog::info(
'Annotations is scanned(autoloader %d, annotation %d, parser %d)',
$stats['autoloader'],
$stats['annotation'],
$stats['parser']
);
// 注解处理完成
return $this->application->afterAnnotation();
}
这一处理器处理的内容非常多,在我本机环境下执行时间约1.2秒.
AnnotationRegister::load实现:
public static function load(array $config = []): void
{
// 初始化和配置注解资源
$resource = new AnnotationResource($config);
// 执行注解的加载方法
$resource->load();
}
AnnotationResource的构造方法:
public function __construct(array $config = [])
{
// 设置默认不包含的PSR4前缀
// 这里有'Psr\\','Monolog\\','PHPUnit\\','Symfony\\'
// Init $excludedPsr4Prefixes
$this->excludedPsr4Prefixes = self::DEFAULT_EXCLUDED_PSR4_PREFIXES;
// 将传递进来的config设置到当前对象上
// 后附$config的打印结果
// 打印结果可见:未额外设置失效的loader和不引入的psr4前缀
// 设置了当前是否在phar环境,项目根目录和通知回调函数
// Can set property by array
ObjectHelper::init($this, $config);
// 向Doctrine组件注册了一个检查类是否存在的回调
$this->registerLoader();
// 获取composer的classLoader
$this->classLoader = ComposerHelper::getClassLoader();
// 获取已加载的文件
$this->includedFiles = get_included_files();
}
$config的打印结果:
array(5) {
// 当前不是在phar中执行 所以此处为false
["inPhar"]=>
bool(false)
// 项目根目录
["basePath"]=>
string(54) "/Volumes/Samsung_T5/hmqr/phpProgram/fulian/AuthService"
// 通知的回调函数,是AnnotationProcessor处理器上的notifyHandle方法
["notifyHandler"]=>
array(2) {
[0]=>
object(Swoft\Processor\AnnotationProcessor)#10 (1) {
["application":protected]=>
object(App\Application)#3 (12) {
["basePath":protected]=>
string(54) "/Volumes/Samsung_T5/hmqr/phpProgram/fulian/AuthService"
["envFile":protected]=>
string(10) "@base/.env"
["beanFile":protected]=>
string(13) "@app/bean.php"
["appPath":protected]=>
string(9) "@base/app"
["configPath":protected]=>
string(12) "@base/config"
["runtimePath":protected]=>
string(13) "@base/runtime"
["resourcePath":protected]=>
string(14) "@base/resource"
["processor":"Swoft\SwoftApplication":private]=>
object(Swoft\Processor\ApplicationProcessor)#14 (2) {
["processors":"Swoft\Processor\ApplicationProcessor":private]=>
array(6) {
[0]=>
object(Swoft\Processor\EnvProcessor)#8 (1) {
["application":protected]=>
*RECURSION*
}
[1]=>
object(Swoft\Processor\ConfigProcessor)#9 (1) {
["application":protected]=>
*RECURSION*
}
[2]=>
*RECURSION*
[3]=>
object(Swoft\Processor\BeanProcessor)#11 (1) {
["application":protected]=>
*RECURSION*
}
[4]=>
object(Swoft\Processor\EventProcessor)#12 (1) {
["application":protected]=>
*RECURSION*
}
[5]=>
object(Swoft\Processor\ConsoleProcessor)#13 (1) {
["application":protected]=>
*RECURSION*
}
}
["application":protected]=>
*RECURSION*
}
["startConsole":protected]=>
bool(true)
["disabledProcessors":"Swoft\SwoftApplication":private]=>
array(0) {
}
["disabledAutoLoaders":"Swoft\SwoftApplication":private]=>
array(0) {
}
["disabledPsr4Prefixes":"Swoft\SwoftApplication":private]=>
array(0) {
}
}
}
[1]=>
string(12) "notifyHandle"
}
// 无效的AutoLoaders
["disabledAutoLoaders"]=>
array(0) {
}
// 不引入的Psr4前缀
["excludedPsr4Prefixes"]=>
array(0) {
}
}
registerLoader代码:
private function registerLoader(): void
{
// 此处看到swoft的注解依赖了Doctrine框架的注解功能
// 这里给Doctrine\Common\Annotations\AnnotationRegistry
// 注册了回调函数,回调的功能是根据传递进来的类返回该类是否存在
// 这是Doctrine框架的用法,如有疑问,可先尝试使用Doctrine
// 所以为什么不直接 return class_exists($class);
/** @noinspection PhpDeprecationInspection */
AnnotationRegistry::registerLoader(function (string $class) {
if (class_exists($class)) {
return true;
}
return false;
});
}
初始化完成后调用load方法:
public function load(): void
{
// 获取命名空间和源码目录的映射
$prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
// 遍历获取的映射
foreach ($prefixDirsPsr4 as $ns => $paths) {
// 如果遍历到的命名空间是在被排除的命名空间内
// 则发送回调消息,不再继续加载这个命名空间
// 继续执行下一个命名空间
// Only scan namespaces
if ($this->onlyNamespaces && !in_array($ns, $this->onlyNamespaces, true)) {
$this->notify('excludeNs', $ns);
continue;
}
// 如果当前命名空间是被定义在不引入的命名空间前缀
// 则发送通知,跳过执行当前命名空间的加载
// It is excluded psr4 prefix
if ($this->isExcludedPsr4Prefix($ns)) {
AnnotationRegister::addExcludeNamespace($ns);
$this->notify('excludeNs', $ns);
continue;
}
// 遍历命名空间的源码根目录,寻找loader的类文件
// 未经设置的话,此处寻找的是源码包目录下的
// AutoLoader.php文件
// Find package/component loader class
foreach ($paths as $path) {
// 拼接loader文件的全路径
$loaderFile = $this->getAnnotationClassLoaderFile($path);
// 如果包目录内不存在AutoLoader.php文件
if (!file_exists($loaderFile)) {
$this->notify('noLoaderFile', $this->clearBasePath($path), $loaderFile);
continue;
}
// 拼接命名空间和loader的类名
$loaderClass = $this->getAnnotationLoaderClassName($ns);
// 如果类不存在
// 发送通知,跳过当前自动加载类
if (!class_exists($loaderClass)) {
$this->notify('noLoaderClass', $loaderClass);
continue;
}
$isEnabled = true;
// 实例化自动加载类
$autoLoader = new $loaderClass();
// 如果类不是LoaderInterface的实现者
// 跳过当前加载
if (!$autoLoader instanceof LoaderInterface) {
$this->notify('invalidLoader', $loaderFile);
continue;
}
// 通知查找loader成功消息
$this->notify('findLoaderClass', $this->clearBasePath($loaderFile));
// 如果当前loader不是一个可用的loader
// 则通知并修改enabled状态
// If is disable, will skip scan annotation classes
if (isset($this->disabledAutoLoaders[$loaderClass]) || !$autoLoader->isEnable()) {
// 修改enabled状态
$isEnabled = false;
// 通知消息
$this->notify('disabledLoader', $loaderFile);
} else {
// 注册loaderFile
AnnotationRegister::addAutoLoaderFile($loaderFile);
// 通知添加loaderFile成功消息
$this->notify('addLoaderClass', $loaderClass);
// 获取并搜集类bean
// Scan and collect class bean s
$this->loadAnnotation($autoLoader);
}
// Storage autoLoader instance to register
// 注册loader
AnnotationRegister::addAutoLoader($ns, $autoLoader, $isEnabled);
}
}
}
loadAnnotation实现:
private function loadAnnotation(LoaderInterface $loader): void
{
// 获取命名空间和对应根目录的映射关系
$nsPaths = $loader->getPrefixDirs();
// 遍历得到命名空间和目录
foreach ($nsPaths as $ns => $path) {
// 调用spl的RecursiveIteratorIterator迭代器
// 获取指定目录内的所有文件
$iterator = DirectoryHelper::recursiveIterator($path);
// 遍历迭代器
/* @var SplFileInfo $splFileInfo */
foreach ($iterator as $splFileInfo) {
// 获取当前文件全路径
$filePath = $splFileInfo->getPathname();
// $splFileInfo->isDir();
// 如果是目录,则跳过,由于迭代器是以
// RecursiveIteratorIterator::LEAVES_ONLY
// 模式打开的,所以不会存在目录的情况
// 但是会返回.和..文件
if (is_dir($filePath)) {
continue;
}
// 获取文件的文件名,不包含路径
$fileName = $splFileInfo->getFilename();
// 获取文件的后缀名
$extension = $splFileInfo->getExtension();
// 如果后缀和定义的loader后缀不同
// 或者文件名中不包含.则跳过
if ($this->loaderClassSuffix !== $extension || strpos($fileName, '.') === 0) {
continue;
}
// 如果是不引入的文件
// 则通知并跳过
// It is exclude filename
if (isset($this->excludedFilenames[$fileName])) {
AnnotationRegister::addExcludeFilename($fileName);
continue;
}
// 拼接loader带.的后缀
$suffix = sprintf('.%s', $this->loaderClassSuffix);
// 获取文件所在目录
$pathName = str_replace([$path, '/', $suffix], ['', '', ''], $filePath);
// 获取类的全名(包含命名空间)
$className = sprintf('%s%s', $ns, $pathName);
// autoload标识当前文件是否已经存在于
// 已经引入的文件集合内
// Fix repeat included file bug
$autoload = in_array($filePath, $this->includedFiles, true);
// 如果文件已经被引入过,类仍然不存在,则通知并跳过
// 如果文件未被引入过,则调用类的__autoload引入文件后,再判断类是否存在,如果仍然不存在,则通知并跳过
// Will filtering: interfaces and traits
if (!class_exists($className, !$autoload)) {
$this->notify('noExistClass', $className);
continue;
}
// 解析注解
// Parse annotation
$this->parseAnnotation($ns, $className);
}
}
}
parseAnnotation实现:
private function parseAnnotation(string $namespace, string $className): void
{
// 创建反射类
// Doctine需要用到
// Annotation reader
$reflectionClass = new ReflectionClass($className);
// 如果是一个abstract类,则不继续解析注解
// Fix ignore abstract
if ($reflectionClass->isAbstract()) {
return;
}
$oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);
if (!empty($oneClassAnnotation)) {
AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
}
}
parseOneClassAnnotation实现:
private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
{
// 实例化Doctrine的reader
// Annotation reader
$reader = new AnnotationReader();
// 获取类名
$className = $reflectionClass->getName();
// 返回结果
$oneClassAnnotation = [];
// 获取类的注解
$classAnnotations = $reader->getClassAnnotations($reflectionClass);
// 遍历类的注解
// Register annotation parser
foreach ($classAnnotations as $classAnnotation) {
// 如果是AnnotationParser的实例
if ($classAnnotation instanceof AnnotationParser) {
// 将此注解对象注册到注解parser
$this->registerParser($className, $classAnnotation);
// 不再继续遍历当前类的其它注解,并返回[]
return [];
}
}
// 如果存在类注解,则将所有的类注解和反射类本身放入返回值中
// Class annotation
if (!empty($classAnnotations)) {
$oneClassAnnotation['annotation'] = $classAnnotations;
$oneClassAnnotation['reflection'] = $reflectionClass;
}
// Property annotation
// 获取类的所有属性
$reflectionProperties = $reflectionClass->getProperties();
// 遍历类的属性
foreach ($reflectionProperties as $reflectionProperty) {
// 获取属性名
$propertyName = $reflectionProperty->getName();
// 获取属性的注解
$propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);
// 如果属性的注解非空,则将属性的注解和属性的反射对象放入返回值中
if (!empty($propertyAnnotations)) {
$oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
$oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
}
}
// Method annotation
// 获取反射类的所有方法
$reflectionMethods = $reflectionClass->getMethods();
// 遍历反射类的所有方法
foreach ($reflectionMethods as $reflectionMethod) {
// 获取方法名
$methodName = $reflectionMethod->getName();
// 获取方法的注解对象
$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;
}
总结:
1.swoft的注解底层依赖于Doctrine的annotations包.
2.注解处理器的工作流程:
(1).run()调用AnnotationProsser的handle方法.
(2).handle()调用Swoft\Annotation\AnnotationRegister::load方法.
(3).第(2)步的load方法调用Swoft\Annotation\Resource\AnnotationResource的load方法.
(4).第(3)步的load方法通过composer的autoloader获取到所有的命名空间和源码目录的映射.
然后遍历获取目录内的AutoLoader类,然后调用loadAnnotation传入autoloader实例.
同时将这个命名空间下的autoloader注册到Swoft\Annotation\AnnotationRegister.
(5).loadAnnotation根据传进来的loader获取到其对应目录内的所有文件.
然后遍历这些文件找到合法的库类文件,同时将目录内未加载的类加载.
然后将命名空间和类名传给parseAnnotation方法处理.
(6).parseAnnotation首先创建传进来的类的反射对象,然后调用parseOneClassAnnotation方法.
parseOneClassAnnotation方法调用Doctrine的reader方法.
获取当前类的类注解、属性注解、方法注解.
然后递归调用自身,获取父类的注解对象.
(7).parseAnnotation调用Swoft\Annotation\AnnotationRegister::registerAnnotation方法,将这个类的注解进行注册.
3.经过上面的步骤,注解处理器的工作就全部完成.
注解处理器的工作就是获取所有合法的注解对象,然后进行注册,并未使用获取的注解对象.
ps:只有被实际调用过的注解才会被收集,每多一个该注解的调用,注解解析器的parse方法就会多执行一次,如果未被调用,则一次也不会被执行.