AOP 中有很多概念 例如(通知)(切面)(连接点)等等,但是作为一名刚入手 AOP 的同学想要理解这些概念其实是很困难的,本文以实战的方式介绍 AOP 不讨论概念,致力于让读者可以 顺利的在 swoft2 中使用AOP
AOP 的操作对象是方法,所以不管哪种形式的声明,都是对相应的方法增加功能. 例如现在有个 A 方法.我们如果想在执行 A 方法之前做某些事,例如计算 A 方法开始的执行时间,然后再 A 方法执行后再进行某些操作,例如统计执行时间.这个时候我们就可以考虑 AOP ,AOP 可以让我们在不修改 A 方法本身,在 A 方法之前或者之后添加一系列操作逻辑.这样的好处显而易见,我们 A 方法只需要关注他本身自己的逻辑,而一些辅助操作,可以在 AOP 中完成,这样代码更易维护.主逻辑与辅助逻辑分离,可以更好的复用.
文件 App/Aspect/MethodAspect.php
我们这里只给出了 方法AOP 的实例,其他 类AOP 和 注解AOP 的实例其实功能一样.大家只需要修改 @Point* 注解就可以了
<?php declare(strict_types=1); namespace App/Aspect; use Swoft/Aop/Annotation/Mapping/After; use Swoft/Aop/Annotation/Mapping/AfterReturning; use Swoft/Aop/Annotation/Mapping/AfterThrowing; use Swoft/Aop/Annotation/Mapping/Around; use Swoft/Aop/Annotation/Mapping/Aspect; use Swoft/Aop/Annotation/Mapping/Before; use Swoft/Aop/Annotation/Mapping/PointExecution; use Swoft/Aop/Point/JoinPoint; use Swoft/Aop/Point/ProceedingJoinPoint; /** * Class MethodAspect * @Aspect() * @PointExecution( * include={"HomeController::index"} * ) * @since 2.0 */ class MethodAspect { /** * @Before() */ public function before() { echo "before方法调用"; } /** * @After() * @param JoinPoint $joinPoint */ public function after(JoinPoint $joinPoint,$id) { var_dump($joinPoint->getTarget()); var_dump($joinPoint->getArgs()); var_dump($joinPoint->getMethod()); var_dump($joinPoint->getClassName()); echo "after方法调用"; } /** * @Around() * @param ProceedingJoinPoint $joinPoint * @return mixed * @throws /Throwable */ public function around(ProceedingJoinPoint $joinPoint){ echo "around 前置方法调用/n"; $result = $joinPoint->proceed();//这里result 是HomeController::index的返回值 echo "around 后置方法调用/n"; return $result; } /** * @AfterReturning() * @param JoinPoint $joinPoint * @return mixed * @throws /Throwable */ public function afterReturning(JoinPoint $joinPoint,$id){ echo "afterReturning 方法调用"; return $joinPoint->getReturn()->withContent("afterReturning 方法调用"); } /** * @AfterThrowing() * @return mixed * @throws /Throwable */ public function afterThrowing($id){ echo "捕获到异常调用"; } }
around 前置方法调用 before方法调用 around 后置方法调用 after方法调用 afterReturning 方法调用
Aspect 类与任何其他正常的 bean 类似,并且可能像任何其他类一样拥有方法和字段,但必须使用 @Aspect 注解 (这个必须的)
定义一个类为切面类
参数
order 优先级,多个切面,越小预先执行
swoft2中的 AOP 根据作用范围分为三种
/* * @PointExecution( * include={OrderService::createOrder,UserService::getUserBalance}, * exclude={OrderService::generateOrder} * ) */
定义匹配切入点, 指明要代理目标类的哪些方法
include 定义需要切入的匹配集合,匹配的类方法,支持正则表达式
exclude 定义需要排序的匹配集合,匹配的类方法,支持正则表达式
注意 实体名称(类名)必须指定 namespace 完整路径 例如 ‘App/Controller/HomeController::index’ 或者先用 use 将目标类 use 进来
这里需要注意下,如果需要使用正则,则传入的必须使用双引号 “ “ 引起来,命名空间分隔符必须使用 / 转义,同时双引号内必须是类的完整路径。
/* * @PointBean( * include={OrderService::class,UserService::class}, * exclude={} * ) */
定义bean切入点, 这个bean类里的所有public方法执行都会经过此切面类的代理
include 定义需要切入的注解类名集合
exclude 定义需要排除的注解类名集合
注意 实体名称(类名)必须指定 namespace 完整路径 例如 ‘App/Http/Controller/HomeController’ 或者先用 use 将目标类 use 进来
/* * @PointAnnotation( * include={RequestMapping::class}, * exclude={} * ) */
定义注解类切入点, 所有包含使用了对应注解的方法都会经过此切面类的代理
include 定义需要切入的注解类名集合
exclude 定义需要排除的注解类名集合
注意 实体名称(类名)必须指定 namespace 完整路径 例如 ‘Swoft/Http/Server/Annotation/Mapping/RequestMapping::class’ 或者先用 use 将目标类 use 进来
AOP只拦截 public 方法,不拦截private方法。
另外在 Swoft AOP 中 如果切入了多个方法,此时在某一个方法内调用了另一个被切入的方法,此时AOP 也会织入通知。例如 我们定义了一个类 A 它有两个 public 方法 fun1(),fun2(),然后我们定义一个切面,使用 @PointBean(include={A::class}) 注解将 A 类中的两个方法都进行切入,这是我们看下这两个方法的定义:
<?php class A { function fun1() { echo 'fun1'."/n"; } function fun2() { $this->fun1(); echo 'fun2'."/n"; } }
此时如果我们访问 fun2() 这个方法,那么我们的切面就会执行两次,切面的执行顺序和方法的执行顺序相同。先执行 fun2() 方法的织入通知,再执行 fun1() 方法的织入通知。
我们假设 AOP 的目标方法是 A 方法 以方便下面分析.并且 A 方法有一个参数叫 $id
注解 before 定义的方法没有参数,在 A方法执行前执行
如果定义了注解around方法,则执行顺序是 先执行输出 around 前置方法调用 , 在执行输出 before方法调
注解 around 定义方法在整个A方法前后都执行.
参数
ProceedingJoinPoint $joinPoint 注意这个参数和其他方法的区别
这个方法需要注意的是必须要调用 $joinPoint->proceed() 不然会出现A方法不执行的问题.这个操作的返回值,就是 A方法 的返回值并且我们可以修改A方法的实际参数,这个 proceed 函数可以传一个数组,数组对应了 A 方法的形参,我们可以动态修改参数, $joinPoint->getArgs()可以获得原始参数,在原始参数上加工,传给 proceed 方法
注解 after 定义方法 在执行A方法以后执行
参数
JoinPoint $joinPoint
注解 afterReturning 定义的方法,在执行A方法完成以后并且有返回值才执行的方法
参数
JoinPoint $joinPoint
注解 afterThrowing 定义方法 在A 方法抛出异常后执行的方法
@Before:前置通知。在目标方法之前执行
@After:后置通知。在目标方法之后执行
@AfterReturing:返回通知
@AfterThrowing:异常通知。目标方法异常时执行
@Around:环绕通知。等同于前置通知加上后置通知,在目标方法之前及之后执行
$joinPoint->getTarget() //返回操作的类 上面的例子是 HomeController $joinPoint->getArgs() //返回参数 例如你在A函数注入 AOP 这里就返回A函数的调用参数 $joinPoint->getReturn() //返回A函数的返回值 $joinPoint->getMethod() //返回调用的方法名 上面的例子是 index $joinPoint->setReturn("返回值") //修改返回值在下次 getreturn 时返回的就是修改过的返回值 $joinPoint->getClassName() //返回调用的类名 上面的例子是 "App/Http/Controller/HomeController"
文件 app/Aspect/TimerAspect.php
<?php declare(strict_types=1); namespace App/Aspect; use App/Http/Controller/HomeController; use Swoft/Aop/Annotation/Mapping/After; use Swoft/Aop/Annotation/Mapping/AfterReturning; use Swoft/Aop/Annotation/Mapping/AfterThrowing; use Swoft/Aop/Annotation/Mapping/Around; use Swoft/Aop/Annotation/Mapping/Aspect; use Swoft/Aop/Annotation/Mapping/Before; use Swoft/Aop/Annotation/Mapping/PointBean; use Swoft/Aop/Point/JoinPoint; use Swoft/Aop/Point/ProceedingJoinPoint; /** * Class AnnotationAspect * @Aspect() * @PointBean( * include={HomeController::class} * ) * @since 2.0 */ class TimerAspect { protected start_time; /** * @Before() */ public function before($id) { $this->start_time=microtime(true); } /** * @After() * @param JoinPoint $joinPoint */ public function after(JoinPoint $joinPoint,$id) { $method = $joinPoint->getMethod(); $after = microtime(true); $runtime = ($after - $this->start_time) * 1000; echo "{$method} 方法,本次执行时间为: {$runtime}ms/n"; } }
文件 app/Http/Controller/HomeController.php
<?php declare(strict_types=1); namespace App/Http/Controller; use App/Annotation/Mapping/MyAnnotation; use App/Annotation/Mapping/MyAnnotation2; use Swoft; use Swoft/Http/Message/ContentType; use Swoft/Http/Message/Response; use Swoft/Http/Server/Annotation/Mapping/Controller; use Swoft/Http/Server/Annotation/Mapping/RequestMapping; use Swoft/View/Renderer; use Swoole/Coroutine; use Throwable; /** * Class HomeController * @Controller() */ class HomeController{ /** * @RequestMapping("/testapsect") * @param string $name * @return Response */ public function testapsect(string $name): Response { Coroutine::sleep(3); return context()->getResponse()->withData(['ok']); } }
testapsect 方法,本次执行时间为: 3000.9479522705ms