若转载教程,请注明出自SW-X框架官方文档
AOP,即面向切面编程,可以说是OOP,面向对象编程的补充和完善。
面向切面编程是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。常见的使用场景:

  1. 日志
  2. 事物
  3. 数据库操作

面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。
所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事物,日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。
这样,会使业务逻辑变得混杂不清。
举个例子:
银行系统取款会有一个流程查询也会有一个流程。
image.png
这两者,都有一个相同的验证用户的流程
这个时候 AOP 就可以来帮我们简化代码了,首先,写代码的时候可以不写这个验证用户的步骤,即完全不考虑验证用户,写完之后,在另外一个地方,写好验证用户的代码,然后告诉 PHP 你要把这一段代码加到哪几个地方,PHP就会帮你加过去,这里还只是两个地方,如果有多个控制流,这样写代码会大大节约时间。
而且 AOP 不会把代码加到源文件里,但是它会正确的影响最终的机器代码。
上面那个 验证用户 的方框,我们可以把它当成一块板子,在这块板子上插入一些控制流程,这块板子就可以当成是 AOP 中的一个切面。
所以 AOP 的本质是在一系列的纵向的控制流程中,把那些相同的子流程提取成一个横向的面,把纵向流程画成一条直线,而 AOP 相当于把相同的地方连起来了
再来一幅图理解一下:
image.png
这个验证用户的子流程 就成了一条直线,也可以理解成一个切面,这里只插了三个流程,如果其他流程也需要这个子流程,也可以插到其他地方去。

AOP术语

术语 说明
切面 切面泛指交叉业务逻辑。比如事物处理,日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强
织入 织入是指将切面代码插入到目标对象的过程
连接点 连接点指切面可以织入的位置
切入点 切入点指切面具体织入的位置
通知 通知(Advice)是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
顾问 顾问(Advisor)是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。不仅指定了切入时间点,还可以指定具体的切入点。

AOP的实现方式

通知类型 说明
前置通知(MethodBeforeAdvice) 目标方法执行之前调用
后置通知(AfterReturningAdvice) 目标方法执行完成之后调用
环绕通知(MethodInterceptor) 目标方法执行前后都会调用方法,且能增强结果
异常处理通知(ThrowsAdvice) 目标方法出现异常调用

深入理解

在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:

日志记录
事务控制
权限控制

然后才是编写核心的业务逻辑处理代码。
当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于核心业务逻辑处理才那么几行,如下图所示。
image.png
方法复方法,类复类,就这样带着无可奈何遗憾地度过了多少个春秋。
这倒也罢,倘若到了项目的尾声,突然决定在权限控制上需要进行大的变动时,成千上万个方法又得一一”登门拜访”,痛苦”雪上加霜”。
如果能把上图中众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:
Java EE框架(PHP的大部分框架也支持)的程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。
在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。
面向切面编程AOP技术就是为解决这个问题而诞生的,AOP切面其实就是横切面,如下图所示,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。
image.png
下面我们以用户管理业务逻辑组件UserService的AOP实现过程(见下图)为例,深度剖析一下AOP技术的实现原理。
image.png
AOP技术是建立在反射机制与动态代理机制之上的。
业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。
从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。

PHP实现案例

没使用AOP之前的代码:

<?php
App::run();
// 框架执行类理解为 APP:run
class App {
    public static function run() {
        Bank::run();
    }
}
// 业务核心类,理解为controller吧
class Bank {
    public static function run() {
        # 验证用户
        if ($_GET['name'] != '小黄牛') {
            die('验证码用户错误了');
        }
        # 取款
        if ($_GET['type'] == 1) {
            echo '用户取款拉';
            error_log('取出300', 3, 'error.log');
        } else if ($_GET['type'] == 2) {
            echo '用户在查询余额';
            error_log('查询显示500', 3, 'error.log');
        }
        echo '退出柜员机~~';
        error_log('正常退出', 3, 'error.log');
    }
}

用AOP优化之后:

<?php
App::run();
// 框架执行类理解为 APP:run
class App {
    public static function run() {
        # 假设这是我们当前访问的Controller
        $controller = 'Bank';
        # 下面我们就不做AOP是否有定义的操作了,还有AOP内是否有规范创建4大实现了
        # 如果要做得到话,自己用PHP的反射机制实现
        $class = $controller.'Aop';
        $AOP = new $class();
        # 执行前置
        $AOP->BeforeAdvice();
        # 执行环绕
        $AOP->InterceptorAdvice();
        # 异常处理
        try{
            # 执行应用
            Bank::run();
        } catch(Exception $e) {
            $AOP->ThrowsAdvice($e);
        }
        # 执行环绕
        $AOP->InterceptorAdvice();
        # 执行后置
        $AOP->ReturningAdvice();
    }
}
// AOP类,定义了4大实现
class BankAop {
    // 前置
    public function BeforeAdvice() {
        # 验证用户
        if ($_GET['name'] != '小黄牛') {
            die('验证码用户错误了');
        }
    }
    // 后置
    public function ReturningAdvice() {
        WebLog::run('正常退出');
    }
    // 环绕
    public function InterceptorAdvice() {
    }
    // 异常处理
    public function ThrowsAdvice($Exception) {
        # 我这里抓住,然后打印
        echo 'Message: ' .$Exception->getMessage();
    }
}
// 日志写入,自己的定义呗
class WebLog {
    public static function run($error) {
        return error_log($error, 3, 'error.log');
    }
}
// 业务核心类,理解为controller吧
class Bank {
    public static function run() {
        # 取款
        if ($_GET['type'] == 1) {
            echo '用户取款拉';
            WebLog::run('取出300');
        } else if ($_GET['type'] == 2) {
            echo '用户在查询余额';
            WebLog::run('查询显示500');
        }
        echo '退出柜员机~~';
    }
}

这样一来,核心业务的代码就少很多了,上面的例子是将AOP绑定在了controller上。真实开发中我们可能一个AOP公用到多个controller上,也可能独立被某个模块单独触发引用,所以上述案例只是通过controller讲解了AOP的应用,并不是说明只能这样用,不用被固有思维影响了自主逻辑。