PHP注解AOP使用

@心欲无痕  July 24, 2018

在PHP中,可以利用反射或者C层面直接操作文档注释来解析注解,本文从PHP扩展Xan展示利用注解的AOP编程

用户需要先安装 Xan 扩展, 扩展下载地址:https://github.com/liqiongfan/xan

AOP切面思想

切面思想:将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性;AOP是一个横向的关系组合,也就是将应用中的公共服务进行分离。

重要概念:

1、连接点:程序的一个执行点, Xan 中仅支持方法级别的连接点

2、切入点:捕获连接点的结构,一般可以通过正则匹配或者表达式,目前 Xan 并不支持

3、通知:切入点的业务逻辑代码,目前 Xan 支持(@before, @after, @success, @failure)四种通知

4、切面:定义一个切面类,使用注解: @Aspect 来定义一个切面

5、引入:通过引入附加的属性等,达到修改对象或者类结构的目的,目前 Xan 可以通过注解 AttrAnnotation(动态添加属性)ConstAnnotation(动态添加常量) 来完成此功能

生成一个切面类:

/**
 *
 * @Aspect
 */
class Base
{
}

一旦生成了一个切面类,那么就可以利用切面的通知功能:

/**
 *
 * @Aspect
 */
class Base
{
    function before()
    {
        echo 'before';
    }
    
    function after($data)
    {
        echo 'after =>' . $data;    
    }
    
    function success()
    {
        echo 'success';
    }
    
    function failure()
    {
        echo 'failure';
    }
    
    /**
     * 定义通知
     * 通知支持传递参数到指定的切入点,如需指定参数,那么必须使用
     * (value="xxx.xxx", parameters="xxx||xxx||xxx")
     * 的方式来指定通知
     * 通知的value表示切入点,分别是类名 + "." + 方法名
     * 通知的parameters表示参数,多个参数使用 "||"分割
     * 
     * @before("Base.before")
     * @after(value="Base.after", parameters="Xan Extension")
     * @success("Base.success")
     */
    function test()
    {
        echo 'I\'m the test method for Aspect programming.';
        // 返回true,那么AOP执行 success通知,否则执行 failure 通知
        return true;
    }   
}

通过代理来进行切面对象的使用:

// 实例化代理,代理返回的对象可以当做常规对象(new生成的对象一样)使用
$base = Xan\Aop\Proxy::instance(Base::class);
// 调用方法
$base->test();

在切面通知中,可以嵌套通知,也就是通知里面可以嵌套通知,达到一个通知链的目的,但是通知链不可闭合(死循环),如果闭合,那么核心会爆出 Fatal Error: Recursive calling:xxx 的提示。

用户可以利用此功能来进行业务逻辑的分离,也就是将两个业务之间的耦合进行脱离,但是一般下建议用户不要通知链层级太多,导致业务的可读性非常低,使用 AOP 的目的是简化编程,但是不可滥用。

嵌套通知(综合示例):


$loader = new \Xan\Loader();
$loader->setMap('@app', __DIR__);
$loader->start();


/**
 * Base class implements the @Aspect
 * if you implements the @Aspect the class will have the feature
 * of the AOP programming
 *
 * @Aspect
 */
class Base
{
    function before()
    {
        echo 'before' . PHP_EOL;
    }

    /**
     * This is the example for using the AOP programming
     * users can define the point-cut when the caller invoke the method.
     * Note that the value key's value was insensitive, so `value="app\base.pointcut"` are 
     * equal to `value="app\Base.pointCut"`
     *
     * @before(value="\app\Base.pointCut", parameters="22||33||88")
     * @after("Base.after")
     * @success("Base.success")
     */
    function list($a, $b, $c)
    {
        echo $a . '=>' . $b . '=>' . $c . PHP_EOL;
        echo 'lists' . PHP_EOL;
        return true;
    }

    function success()
    {
        echo 'success' . PHP_EOL;
    }
    
    function after()
    {
        echo 'after' . PHP_EOL;
    }
}

$base = Xan\Aop\Proxy::instance(Base::class);
$base->list("hello", "world", "Xan");

\app\Base类的代码如下:

namespace app;

/**
 * Class Base
 *
 * @Aspect
 * @\Xan\Type\Annotation\AttrAnnotation(name="Xannotation", version="v1.0.2")
 * @\Xan\Type\Annotation\ConstAnnotation(TOOL="C/C++", URL="https://www.supjos.cn")
 */
class Base
{
    const GAP = 1;

    /**
     *
     * @before("app\Tvv.tvv")
     */
    function pointCut($a, $b, $c)
    {
        echo "app\\Base::pointCut({$a}, {$b}, {$c})" . PHP_EOL;
    }
}

\app\Tvv类的代码如下:

namespace app;

/**
 * Class Tvv
 *
 * @Aspect
 */
class Tvv
{
    /**
     *
     * @before("\app\show.do")
     */
    function tvv()
    {
        echo 'tvv' . PHP_EOL;
    }
}

app\Show类的代码如下:

namespace app;

class Show
{

    function do()
    {
        echo 'do' . PHP_EOL;
    }
}

结果分析:

从上上面可以看出,运行 app\Base::pointCut之前需要先运行 app\Tvv::tvv,而在之前需要运行:app\Show::do,因此通知链就是:pointCut->tvv->do:

do
tvv
app\Base::pointCut(22, 33, 88)
hello=>world=>Xan
lists
success
after

评论已关闭