若转载教程,请注明出自SW-X框架官方文档

AOP,即面向切面编程,可以说是OOP,面向对象编程的补充和完善。

面向切面编程是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。常见的使用场景:

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

面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。

所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事物,日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。

这样,会使业务逻辑变得混杂不清。

举个例子:

银行系统取款会有一个流程查询也会有一个流程。

这两者,都有一个相同的验证用户的流程

这个时候 AOP 就可以来帮我们简化代码了,首先,写代码的时候可以不写这个验证用户的步骤,即完全不考虑验证用户,写完之后,在另外一个地方,写好验证用户的代码,然后告诉 PHP 你要把这一段代码加到哪几个地方,PHP就会帮你加过去,这里还只是两个地方,如果有多个控制流,这样写代码会大大节约时间。

而且 AOP 不会把代码加到源文件里,但是它会正确的影响最终的机器代码。

上面那个 验证用户 的方框,我们可以把它当成一块板子,在这块板子上插入一些控制流程,这块板子就可以当成是 AOP 中的一个切面。

所以 AOP 的本质是在一系列的纵向的控制流程中,把那些相同的子流程提取成一个横向的面,把纵向流程画成一条直线,而 AOP 相当于把相同的地方连起来了

再来一幅图理解一下:

这个验证用户的子流程 就成了一条直线,也可以理解成一个切面,这里只插了三个流程,如果其他流程也需要这个子流程,也可以插到其他地方去。

AOP术语

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

AOP的实现方式

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

深入理解

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

  1. 日志记录
  2. 事务控制
  3. 权限控制

然后才是编写核心的业务逻辑处理代码。

当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于核心业务逻辑处理才那么几行,如下图所示。

方法复方法,类复类,就这样带着无可奈何遗憾地度过了多少个春秋。

这倒也罢,倘若到了项目的尾声,突然决定在权限控制上需要进行大的变动时,成千上万个方法又得一一”登门拜访”,痛苦”雪上加霜”。

如果能把上图中众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:

Java EE框架(PHP的大部分框架也支持)的程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。

在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。

面向切面编程AOP技术就是为解决这个问题而诞生的,AOP切面其实就是横切面,如下图所示,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。

下面我们以用户管理业务逻辑组件UserServiceAOP实现过程(见下图)为例,深度剖析一下AOP技术的实现原理。

AOP技术是建立在反射机制与动态代理机制之上的。

业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。

从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。

PHP实现案例

没使用AOP之前的代码:

  1. <?php
  2. App::run();
  3. // 框架执行类理解为 APP:run
  4. class App {
  5. public static function run() {
  6. Bank::run();
  7. }
  8. }
  9. // 业务核心类,理解为controller吧
  10. class Bank {
  11. public static function run() {
  12. # 验证用户
  13. if ($_GET['name'] != '小黄牛') {
  14. die('验证码用户错误了');
  15. }
  16. # 取款
  17. if ($_GET['type'] == 1) {
  18. echo '用户取款拉';
  19. error_log('取出300', 3, 'error.log');
  20. } else if ($_GET['type'] == 2) {
  21. echo '用户在查询余额';
  22. error_log('查询显示500', 3, 'error.log');
  23. }
  24. echo '退出柜员机~~';
  25. error_log('正常退出', 3, 'error.log');
  26. }
  27. }

用AOP优化之后:

  1. <?php
  2. App::run();
  3. // 框架执行类理解为 APP:run
  4. class App {
  5. public static function run() {
  6. # 假设这是我们当前访问的Controller
  7. $controller = 'Bank';
  8. # 下面我们就不做AOP是否有定义的操作了,还有AOP内是否有规范创建4大实现了
  9. # 如果要做得到话,自己用PHP的反射机制实现
  10. $class = $controller.'Aop';
  11. $AOP = new $class();
  12. # 执行前置
  13. $AOP->BeforeAdvice();
  14. # 执行环绕
  15. $AOP->InterceptorAdvice();
  16. # 异常处理
  17. try{
  18. # 执行应用
  19. Bank::run();
  20. } catch(Exception $e) {
  21. $AOP->ThrowsAdvice($e);
  22. }
  23. # 执行环绕
  24. $AOP->InterceptorAdvice();
  25. # 执行后置
  26. $AOP->ReturningAdvice();
  27. }
  28. }
  29. // AOP类,定义了4大实现
  30. class BankAop {
  31. // 前置
  32. public function BeforeAdvice() {
  33. # 验证用户
  34. if ($_GET['name'] != '小黄牛') {
  35. die('验证码用户错误了');
  36. }
  37. }
  38. // 后置
  39. public function ReturningAdvice() {
  40. WebLog::run('正常退出');
  41. }
  42. // 环绕
  43. public function InterceptorAdvice() {
  44. }
  45. // 异常处理
  46. public function ThrowsAdvice($Exception) {
  47. # 我这里抓住,然后打印
  48. echo 'Message: ' .$Exception->getMessage();
  49. }
  50. }
  51. // 日志写入,自己的定义呗
  52. class WebLog {
  53. public static function run($error) {
  54. return error_log($error, 3, 'error.log');
  55. }
  56. }
  57. // 业务核心类,理解为controller吧
  58. class Bank {
  59. public static function run() {
  60. # 取款
  61. if ($_GET['type'] == 1) {
  62. echo '用户取款拉';
  63. WebLog::run('取出300');
  64. } else if ($_GET['type'] == 2) {
  65. echo '用户在查询余额';
  66. WebLog::run('查询显示500');
  67. }
  68. echo '退出柜员机~~';
  69. }
  70. }

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

免费教程手写不易,希望能支持一下SW-X框架,(^.^)

GitHub有账号的朋友,也可以给我们一个小星星噢!

希望能够与大家共同培育出良好的Swoole生态,对Swoole有兴趣的朋友可以加我微信好友,进入SW-X框架官方交流群。
该群以Swoole生态发展交流为主,若出现争吵,攻击其他人等行为,立即剔除。