AOP 是一种独立的编程范式,和 OOP(面向对象)并列,Spring AOP 只是它的一种实现(AspectJ 也是)。@Transactional 事务、日志、权限校验背后都是它。但"切面、切点、通知"这些词一直有点绕,这篇把 AOP 从头理清:它解决什么、核心概念是什么、底层怎么用动态代理实现。
一、解决什么问题:横切关注点散落各处
看一段没有 AOP 的代码:
public void saveOrder(Order order) {
log.info("开始保存订单"); // 日志
orderDao.insert(order); // 业务
log.info("保存订单成功"); // 日志
}
public void payOrder(long id) {
log.info("开始支付订单"); // 日志
orderDao.updateStatus(id, PAID); // 业务
log.info("支付订单成功"); // 日志
}日志代码散落在每个方法里,和业务逻辑纠缠在一起。日志、事务、权限校验这类逻辑有个共同名字——横切关注点(cross-cutting concern):它们横跨多个业务方法,但又和业务本身无关。
问题是:
- 重复:每个方法都要写一遍日志
- 耦合:改日志格式,要改所有方法
- 难维护:业务代码被非业务逻辑淹没
AOP 的思路:把这些横切逻辑"抽"出来,统一织入到指定的方法上,业务方法只留业务。
二、AOP 是什么:把横切逻辑抽成切面
AOP(Aspect-Oriented Programming,面向切面编程)核心是关注点分离——业务归业务,横切归横切。
用 Spring 注解实现一个日志切面:
@Aspect // 声明这是切面
@Component
public class LogAspect {
// 切点:匹配 service 包下所有方法
@Before("execution(* com.demo.service.*.*(..))")
public void logBefore() {
System.out.println("方法执行前:记录日志");
}
}现在 service 包下所有方法执行前,都会自动打印日志,业务方法里一行日志代码都不用写。
三、核心概念(别被名词绕晕)
| 概念 | 是什么 | 类比 |
|---|---|---|
| 切面(Aspect) | 横切逻辑的模块(日志切面、事务切面) | 一个"插件" |
| 连接点(JoinPoint) | 能插入切面的位置(方法调用) | 所有可插的点 |
| 切点(Pointcut) | 匹配连接点的表达式(哪些方法) | 筛选"插在哪些点" |
| 通知(Advice) | 在切点上执行的动作 | 插进去"做什么" |
一句话串起来:切面 = 切点(在哪)+ 通知(做什么)。
四、五种通知类型
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.demo.service.*.*(..))") // 前置:方法前
public void before() {}
@After("execution(* com.demo.service.*.*(..))") // 后置:方法后(finally)
public void after() {}
@AfterReturning(pointcut = "...", returning = "result") // 返回后:拿到返回值
public void afterReturning(Object result) {}
@AfterThrowing(pointcut = "...", throwing = "e") // 异常后
public void afterThrowing(Exception e) {}
@Around("execution(* com.demo.service.*.*(..))") // 环绕:前后都能控制
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("前");
Object result = pjp.proceed(); // 手动调用目标方法
System.out.println("后");
return result;
}
}最常用的是 @Around 环绕通知——它最强大,能控制目标方法是否执行、篡改返回值、包裹异常。@Transactional 事务就是 @Around 的典型应用。
五、底层:动态代理
AOP 的"织入"不是魔法,底层就是动态代理——在目标对象外面包一层代理,代理在调用目标方法前后插入切面逻辑:
- 有接口:JDK 动态代理(靠"实现接口"冒充,所以目标必须有接口)
- 无接口:CGLIB(靠"继承生成子类"冒充,所以无接口也行,但 final 不行)
两种代理的机制差异(为什么有接口用 JDK、无接口用 CGLIB)详见《Java 动态代理:JDK 代理 vs CGLIB》。这和《代理模式》讲的完全一致——Spring AOP 就是代理模式在框架层面的集大成。
六、典型应用
- @Transactional 事务:方法执行前开启事务,正常提交、异常回滚——一个环绕通知搞定
- 日志记录:统一在 service 层切日志
- 权限校验:在 Controller 方法前校验权限
- 性能监控:统计方法耗时
小结
- AOP 解决"横切关注点散落各处"的问题,把日志/事务抽成切面统一织入
- 切面 = 切点(在哪)+ 通知(做什么)
- 五种通知:@Before/@After/@AfterReturning/@AfterThrowing/@Around(环绕最常用)
- 底层是动态代理(JDK 代理 + CGLIB),就是代理模式的落地
IoC 管"对象怎么来",AOP 管"横切逻辑怎么织入"——这两个是众多框架通用的两大设计思想,Spring 把它们用得最透。想了解 Spring 本身怎么来的,看《Spring 与 Spring Boot 的来历》;想深入 Spring Boot 的核心机制,看《Spring Boot 自动配置原理》。
