AOP(Aspect-Oriented Programming,面向切面编程)‌ 是一种编程思想,它的核心目的是:‌把那些与业务逻辑无关、但多个模块都需要用到的“通用功能”抽取出来,统一处理。

简单来说,AOP 就是‌“在不修改原有代码的情况下,给方法‘插队’执行一些额外逻辑”‌。

1. 为什么要用 AOP?(痛点)

假设你有一个 UserService,里面有 createUser、updateUser、deleteUser 等方法。

现在老板提了三个需求:

  1. ‌记录日志‌:每个方法执行前都要记录“谁在什么时候调用了什么”。
  2. ‌权限检查‌:每个方法执行前都要检查“用户有没有权限”。
  3. ‌事务控制‌:每个方法执行前后要开启/提交事务。

❌ 没有 AOP 的做法(代码污染):

你得在每个方法里都写一遍重复的代码

public function createUser() {
    // 1. 记录日志
    Log::info("开始创建用户");

    // 2. 检查权限
    if (!Auth::check()) throw new Exception("无权限");

    // 3. 开启事务
    DB::beginTransaction();
    try {
        // === 真正的业务逻辑 ===
        $user = User::create([...]);
        // ======================
        DB::commit();
    } catch (\Exception $e) {
        DB::rollBack();
        throw $e;
    }

    // 4. 记录结束日志
    Log::info("创建用户结束");
}

public function updateUser() {
    // 又要写一遍日志、权限、事务... 代码极其冗余!
}

问题‌:

  • 业务代码和通用代码混杂在一起,难以阅读。
  • 如果要修改日志格式,得改几十个地方。
  • 违反了 DRY (Don't Repeat Yourself) 原则。

2. AOP 的做法(优雅解耦)

AOP 允许你把这些通用逻辑(日志、权限、事务)提取到一个独立的‌“切面(Aspect)”‌类中。然后告诉框架:“在所有 Service 方法执行前后,自动插入这些逻辑”。

✅ 使用 AOP 后

你的业务代码变得非常干净:

public function createUser() {
    // 只有纯粹的业务逻辑
    return User::create([...]);
}

public function updateUser() {
    // 只有纯粹的业务逻辑
    return User::update([...]);
}

而日志、权限、事务逻辑被移到了单独的 LogAspect、AuthAspect、TransactionAspect 中。框架会在运行时自动把它们“织入(Weave)”到业务方法中。

3. AOP 的核心概念

为了理解 AOP,你需要知道这几个术语:

术语 解释 例子
切面(Aspect) 包含通用逻辑的类。 LogAspect 类,里面写着怎么记日志。
连接点(Join Point) 程序执行过程中的某个点。 方法调用前、方法调用后、抛出异常时。
通知 (Advice) 在连接点执行的具体动作。 “在方法执行前打印日志”就是一个通知。
切入点(Pointcut) 定义哪些方法需要被拦截。 “所有 App\Service* 下的所有方法”。
织入(Weave) 将切面织入到目标对象中,生成代理对象 Hyperf 生成代理类,把日志代码插进去。

4. Hyperf 中 AOP 是如何实现的?

Hyperf 利用 PHP 反射 和 ‌代理模式(Proxy Pattern) 实现 AOP。

步骤如下:

  1. ‌定义切面‌:你写一个类,标记为 @Aspect,并指定切入点(比如哪些类、哪些方法)。
  2. ‌启动时扫描‌:Hyperf 启动时,通过反射发现这个切面。
  3. ‌生成代理类‌:
  4. Hyperf 会为被拦截的目标类(如 UserService)生成一个‌代理类‌(UserServiceProxy)。
  5. 这个代理类继承了 UserService,并重写了它的方法。 4.‌运行时代替‌:
  6. 当你调用 $container->make(UserService::class) 时,容器实际返回的是 UserServiceProxy。
  7. 当你调用 $proxy->createUser() 时:
    1. 代理类先执行‌切面逻辑‌(如记日志)。
    2. 然后调用‌父类方法‌(真正的 createUser 业务逻辑)。
    3. 最后执行‌后置逻辑‌(如记录耗时)。

代码示例(Hyperf 风格):

// 1. 定义切面
#[Aspect]
class LogAspect extends AbstractAspect
{
    // 切入点:拦截 UserService 的所有方法
    public array $classes = [
        UserService::class . '::*'
    ];

    // 前置通知
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        echo "[LOG] 方法开始: " . $proceedingJoinPoint->methodName . "\n";

        // 执行真正的业务方法
        $result = $proceedingJoinPoint->process();

        echo "[LOG] 方法结束\n";

        return $result;
    }
}

// 2. 业务类(完全不需要知道日志的存在)
class UserService {
    public function createUser() {
        echo "正在创建用户...\n";
        return true;
    }
}

// 3. 调用
$userService = $container->make(UserService::class); // 实际拿到的是代理类
$userService->createUser();

// 输出:
// [LOG] 方法开始: createUser
// 正在创建用户...
// [LOG] 方法结束

5. AOP 的典型应用场景

  1. ‌日志记录‌:自动记录接口请求参数、响应结果、耗时。
  2. ‌权限验证‌:在方法执行前检查用户角色。
  3. ‌缓存处理‌:@Cacheable 注解,先查缓存,有则返回,无则执行方法并写入缓存。
  4. ‌事务管理‌:@Transaction 注解,自动开启/提交/回滚事务。
  5. ‌性能监控‌:统计方法执行时间,上报到监控系统。
  6. ‌异常处理‌:统一捕获特定异常并返回标准错误格式。

总结

  • OOP(面向对象)‌ 关注的是‌纵向‌继承关系(类与类)。
  • AOP(面向切面)‌ 关注的是‌横向‌切割关系(跨模块的通用逻辑)。

AOP 就像是一个“拦截器”或“过滤器”‌,它让你可以在不触碰业务代码的前提下,动态地给方法添加功能。这在大型项目中是保持代码整洁、可维护的关键技术。

在 Hyperf 中,你看到的 #[Cacheable]、#[Transaction]、#[Middleware] 等注解,底层全是靠 AOP 实现的。

results matching ""

    No results matching ""