昨天学习了Spring AOP面向切面编程,对于之前的理解又再次的加深了。
分为几个板块:
1、理解什么是AOP?
2、AOP解决了什么问题?
3、为什么叫面向切面编程?
板块一:理解什么是AOP?
首先AOP是OOP的延续,OOP又为封装、继承、多态。OOP思想是一种垂直纵向的继承体系。虽然OOP编程思想可以解决大多数代码重复的问题,但有一些情况是处理不了的。比如在顶级父类中的多个方法中相同位置出现了重复的代码,OOP就解决不了,这种情况AOP解决。
AOP的主要目的是将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,提高代码的模块化程度。
主要术语
- 切面(Aspect)
- 横切关注点的模块化,例如日志记录、事务管理、权限控制等
- 连接点(JoinPoint)
- 程序执行过程中的特定点,通常是方法的调用或执行
- 通知(Advice)
- 切面在特定连接点采取的动作,包括:
- @Before:前置通知,在方法执行前执行
- @After:后置通知,在方法执行后执行
- @AfterReturning:返回后通知,方法成功返回后执行
- @AfterThrowing:异常通知,方法抛出异常后执行
- @Around:环绕通知,包围方法执行
- 切入点(Pointcut)
- 匹配连接点的表达式,定义在哪些方法上应用通知
- 目标对象(Target Object)
- 被一个或多个切面所通知的对象
简单示例
`
常见应用场景
- 日志记录 - 统一记录方法调用日志
- 事务管理 - @Transactional 注解就是基于AOP实现
- 权限控制 - 检查用户权限
- 性能监控 - 统计方法执行时间
- 异常处理 - 统一异常捕获和处理
- 缓存 - 方法结果缓存
板块二:AOP解决了什么问题?
1. 代码重复问题(DRY原则)
问题场景: 没有AOP时,需要在每个方法中重复写相同的代码 使用AOP后: 代码简洁,关注业务逻辑
2. 关注点分离
横切关注点混杂在业务代码中,导致代码难以维护:
- 🔴 业务逻辑:用户创建、订单处理
- 🔵 技术关注点:日志、事务、安全、缓存
AOP将这些关注点分离,让每个模块专注自己的职责。
3. 可维护性问题
没有AOP: 修改横切逻辑需要改动数百个文件
// 需求:所有方法执行前都要打印IP地址
// 没有AOP:需要修改100个文件,添加1000行代码
使用AOP: 只需修改一个切面类
@Aspect
@Component
public class IpLoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logIp(JoinPoint joinPoint) {
String ip = RequestUtils.getClientIp();
logger.info("请求IP: " + ip);
}
}
// 一处修改,全局生效
4. 业务代码污染问题
问题: 技术性代码污染业务逻辑,降低可读性 使用AOP: 业务逻辑清晰明了
@Transactional
@PreAuthorize("hasRole('USER')")
public Order createOrder(@Valid Order order) {
// 纯粹的业务逻辑
return orderDao.save(order);
}
5. 动态功能增强问题
不修改原有代码的情况下,动态添加新功能(开闭原则)
// 需求:为所有接口添加性能监控
// 不需要修改任何业务代码,只需添加一个切面
@Aspect
@Component
public class PerformanceMonitorAspect {
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long time = System.currentTimeMillis() - start;
// 上报监控系统
metricsService.record(pjp.getSignature().getName(), time);
return result;
}
}
板块三:为什么叫面向切面编程?
让我用图解的方式来解释:
"切面"的形象理解
传统OOP的视角 - 纵向结构
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ UserService │ │ OrderService│ │ ProductSvc │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ 日志记录 │ │ 日志记录 │ │ 日志记录 │
│ 权限检查 │ │ 权限检查 │ │ 权限检查 │
│ 事务管理 │ │ 事务管理 │ │ 事务管理 │
│ 性能监控 │ │ 性能监控 │ │ 性能监控 │
│ 业务逻辑 │ │ 业务逻辑 │ │ 业务逻辑 │
│ 异常处理 │ │ 异常处理 │ │ 异常处理 │
└─────────────┘ └─────────────┘ └─────────────┘
每个类都是一个纵向的柱子,重复的功能散落在各个类中。
AOP的视角 - 横向切入
日志记录(Logging Aspect)
═══════════════════════════════════════════════
权限检查(Security Aspect)
═══════════════════════════════════════════════
事务管理(Transaction Aspect)
═══════════════════════════════════════════════
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ UserService │ │ OrderService│ │ ProductSvc │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ 业务逻辑 │ │ 业务逻辑 │ │ 业务逻辑 │
│ createUser│ │ createOrder│ │ addProduct │
│ updateUser│ │ payOrder │ │ updatePrice │
└─────────────┘ └─────────────┘ └─────────────┘
为什么叫"切面"?
1. "切"
就像用刀横向切开一个蛋糕: 从侧面看蛋糕:
━━━━━━━━━━━ ← 奶油层(日志)
▓▓▓▓▓▓▓▓▓▓ ← 蛋糕体(业务)
━━━━━━━━━━━ ← 果酱层(事务)
▓▓▓▓▓▓▓▓▓▓ ← 蛋糕体(业务)
━━━━━━━━━━━ ← 巧克力层(权限)
横切关注点就像这些"层"
横向穿过多个业务模块
2. "面"
这些横向的功能形成了一个横截面:
想象一个三维空间:
Y轴(业务功能)
↑
│ ProductService
│ OrderService
│ UserService
│
└────────────────→ X轴(时间 / 调用顺序)
╱
╱ Z轴(横切关注点)
↙
- 日志
- 事务
- 权限
- 缓存
"切面" = 在业务功能的"柱子"上横向切一刀,切出的那个平面。
代码层面的体现
// 业务代码(纵向)
class UserService {
void createUser() { }
void deleteUser() { }
}
class OrderService {
void createOrder() { }
void cancelOrder() { }
}
// 切面代码(横向切入)
@Aspect
class LoggingAspect {
// 这个切面"横向切入"所有Service的所有方法
@Around("execution(* *..Service.*(..))")
public Object logAround(ProceedingJoinPoint pjp) {
// 横向织入日志逻辑
log("开始");
Object result = pjp.proceed();
log("结束");
return result;
}
}