AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程思想,它的核心目的是:把那些与业务逻辑无关、但多个模块都需要用到的“通用功能”抽取出来,统一处理。
简单来说,AOP 就是“在不修改原有代码的情况下,给方法‘插队’执行一些额外逻辑”。
1. 为什么要用 AOP?(痛点)
假设你有一个 UserService,里面有 createUser、updateUser、deleteUser 等方法。
现在老板提了三个需求:
- 记录日志:每个方法执行前都要记录“谁在什么时候调用了什么”。
- 权限检查:每个方法执行前都要检查“用户有没有权限”。
- 事务控制:每个方法执行前后要开启/提交事务。
❌ 没有 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。
步骤如下:
- 定义切面:你写一个类,标记为 @Aspect,并指定切入点(比如哪些类、哪些方法)。
- 启动时扫描:Hyperf 启动时,通过反射发现这个切面。
- 生成代理类:
- Hyperf 会为被拦截的目标类(如 UserService)生成一个代理类(UserServiceProxy)。
- 这个代理类继承了 UserService,并重写了它的方法。 4.运行时代替:
- 当你调用 $container->make(UserService::class) 时,容器实际返回的是 UserServiceProxy。
- 当你调用 $proxy->createUser() 时:
- 代理类先执行切面逻辑(如记日志)。
- 然后调用父类方法(真正的 createUser 业务逻辑)。
- 最后执行后置逻辑(如记录耗时)。
代码示例(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 的典型应用场景
- 日志记录:自动记录接口请求参数、响应结果、耗时。
- 权限验证:在方法执行前检查用户角色。
- 缓存处理:@Cacheable 注解,先查缓存,有则返回,无则执行方法并写入缓存。
- 事务管理:@Transaction 注解,自动开启/提交/回滚事务。
- 性能监控:统计方法执行时间,上报到监控系统。
- 异常处理:统一捕获特定异常并返回标准错误格式。
总结
- OOP(面向对象) 关注的是纵向继承关系(类与类)。
- AOP(面向切面) 关注的是横向切割关系(跨模块的通用逻辑)。
AOP 就像是一个“拦截器”或“过滤器”,它让你可以在不触碰业务代码的前提下,动态地给方法添加功能。这在大型项目中是保持代码整洁、可维护的关键技术。
在 Hyperf 中,你看到的 #[Cacheable]、#[Transaction]、#[Middleware] 等注解,底层全是靠 AOP 实现的。