转载

Swoft 2 小白教程系列:AOP 详解

Swoft 微信技术交流

AOP 中有很多概念 例如(通知)(切面)(连接点)等等,但是作为一名刚入手 AOP 的同学想要理解这些概念其实是很困难的,本文以实战的方式介绍 AOP 不讨论概念,致力于让读者可以 顺利的在 swoft2 中使用AOP

AOP 说明

AOP 的操作对象是方法,所以不管哪种形式的声明,都是对相应的方法增加功能. 例如现在有个 A 方法.我们如果想在执行 A 方法之前做某些事,例如计算 A 方法开始的执行时间,然后再 A 方法执行后再进行某些操作,例如统计执行时间.这个时候我们就可以考虑 AOP ,AOP 可以让我们在不修改 A 方法本身,在 A 方法之前或者之后添加一系列操作逻辑.这样的好处显而易见,我们 A 方法只需要关注他本身自己的逻辑,而一些辅助操作,可以在 AOP 中完成,这样代码更易维护.主逻辑与辅助逻辑分离,可以更好的复用.

swoft2 中的 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 注解 (这个必须的)

@Aspect

定义一个类为切面类

参数

  1. order 优先级,多个切面,越小预先执行

声明切入点

swoft2中的 AOP 根据作用范围分为三种

给具体某个方法添加AOP#(PointExecution)

/*
* @PointExecution(
*     include={OrderService::createOrder,UserService::getUserBalance},
*     exclude={OrderService::generateOrder}
* )
*/

定义匹配切入点, 指明要代理目标类的哪些方法

include 定义需要切入的匹配集合,匹配的类方法,支持正则表达式

exclude 定义需要排序的匹配集合,匹配的类方法,支持正则表达式

注意 实体名称(类名)必须指定 namespace 完整路径 例如 ‘App/Controller/HomeController::index’ 或者先用 use 将目标类 use 进来

这里需要注意下,如果需要使用正则,则传入的必须使用双引号 “ “ 引起来,命名空间分隔符必须使用 / 转义,同时双引号内必须是类的完整路径。

给类中的所有方法添加AOP#(PointBean)

/*
* @PointBean(
*     include={OrderService::class,UserService::class},
*     exclude={}
* )
*/

定义bean切入点, 这个bean类里的所有public方法执行都会经过此切面类的代理

include 定义需要切入的注解类名集合

exclude 定义需要排除的注解类名集合

注意 实体名称(类名)必须指定 namespace 完整路径 例如 ‘App/Http/Controller/HomeController’ 或者先用 use 将目标类 use 进来

给定义对应注解的方法添加AOP#(PointAnnotation)

/*
* @PointAnnotation(
*     include={RequestMapping::class},
*     exclude={}
* )
*/

定义注解类切入点, 所有包含使用了对应注解的方法都会经过此切面类的代理

include 定义需要切入的注解类名集合

exclude 定义需要排除的注解类名集合

注意 实体名称(类名)必须指定 namespace 完整路径 例如 ‘Swoft/Http/Server/Annotation/Mapping/RequestMapping::class’ 或者先用 use 将目标类 use 进来

@Pointbean、@Pointannotation、@Pointexecution 三种定义的关系是并集,三种里面定义的排除也是并集后在排除。建议为了便于理解和使用,一个切面类尽量只使用上面三个中的一个

AOP 注意事项

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 常用方法说明

$joinPoint->getTarget() //返回操作的类 上面的例子是 HomeController
 
$joinPoint->getArgs() //返回参数 例如你在A函数注入 AOP 这里就返回A函数的调用参数
 
$joinPoint->getReturn() //返回A函数的返回值
 
$joinPoint->getMethod() //返回调用的方法名 上面的例子是 index
 
$joinPoint->setReturn("返回值") //修改返回值在下次 getreturn 时返回的就是修改过的返回值
 
$joinPoint->getClassName() //返回调用的类名 上面的例子是 "App/Http/Controller/HomeController"

执行流程

主方法正常执行流程

Swoft 2 小白教程系列:AOP 详解

主方法抛出异常的执行流程

Swoft 2 小白教程系列:AOP 详解

多个AOP调用执行流程

Swoft 2 小白教程系列:AOP 详解

简单示例-计算方法执行时间

文件 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

Swoft 微信技术交流

原文  https://mp.weixin.qq.com/s/VYCjAkY_xRI1xSRhFak1vg
正文到此结束
Loading...