Skip to content

AOP 是一种独立的编程范式,和 OOP(面向对象)并列,Spring AOP 只是它的一种实现(AspectJ 也是)。@Transactional 事务、日志、权限校验背后都是它。但"切面、切点、通知"这些词一直有点绕,这篇把 AOP 从头理清:它解决什么、核心概念是什么、底层怎么用动态代理实现。

一、解决什么问题:横切关注点散落各处

看一段没有 AOP 的代码:

java
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 注解实现一个日志切面:

java
@Aspect                     // 声明这是切面
@Component
public class LogAspect {

    // 切点:匹配 service 包下所有方法
    @Before("execution(* com.demo.service.*.*(..))")
    public void logBefore() {
        System.out.println("方法执行前:记录日志");
    }
}

现在 service 包下所有方法执行前,都会自动打印日志,业务方法里一行日志代码都不用写

三、核心概念(别被名词绕晕)

概念是什么类比
切面(Aspect)横切逻辑的模块(日志切面、事务切面)一个"插件"
连接点(JoinPoint)能插入切面的位置(方法调用)所有可插的点
切点(Pointcut)匹配连接点的表达式(哪些方法)筛选"插在哪些点"
通知(Advice)在切点上执行的动作插进去"做什么"

一句话串起来:切面 = 切点(在哪)+ 通知(做什么)

四、五种通知类型

java
@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 就是代理模式在框架层面的集大成。

AOP 切面:日志/事务切面横切多个业务方法,织入到切点

六、典型应用

  • @Transactional 事务:方法执行前开启事务,正常提交、异常回滚——一个环绕通知搞定
  • 日志记录:统一在 service 层切日志
  • 权限校验:在 Controller 方法前校验权限
  • 性能监控:统计方法耗时

小结

  • AOP 解决"横切关注点散落各处"的问题,把日志/事务抽成切面统一织入
  • 切面 = 切点(在哪)+ 通知(做什么)
  • 五种通知:@Before/@After/@AfterReturning/@AfterThrowing/@Around(环绕最常用)
  • 底层是动态代理(JDK 代理 + CGLIB),就是代理模式的落地

IoC 管"对象怎么来",AOP 管"横切逻辑怎么织入"——这两个是众多框架通用的两大设计思想,Spring 把它们用得最透。想了解 Spring 本身怎么来的,看《Spring 与 Spring Boot 的来历》;想深入 Spring Boot 的核心机制,看《Spring Boot 自动配置原理》。