Files
blog-press/docs/Web/SpringBoot/Spring-AOP.md

11 KiB
Raw Blame History

title, date
title date
Spring AOP简介 2025-12-25

一、基本概念

AOPAspect-Oriented Programming面向切面编程是一种编程范式核心思想是将与业务核心逻辑无关但又被多个模块共享的功能(如日志、事务、权限校验、性能监控)抽取出来,形成独立的 “切面”,在不修改原有业务代码的前提下,通过 “织入” 的方式动态添加到目标方法的执行流程中。
Spring AOP 是 Spring 框架对 AOP 思想的实现,基于动态代理JDK 动态代理 + CGLIB 动态代理),专注于解决企业级开发中的横切关注点问题。

术语 说明
切面Aspect 封装横切逻辑的类(如日志切面、事务切面),包含通知和切点
连接点Joinpoint 程序执行过程中的某个特定点如方法调用、异常抛出Spring AOP 中仅支持方法级连接点
切点Pointcut 匹配连接点的规则,用于指定切面要织入到哪些方法上
通知Advice 切面的具体逻辑(如日志打印、权限校验),按执行时机分为 5 种
目标对象Target 被切面织入的目标类(业务类)
代理对象Proxy Spring AOP 为目标对象创建的代理类,横切逻辑通过代理执行
织入Weaving 将切面逻辑添加到目标对象的过程Spring AOP 是运行时织入(通过动态代理)
通知类型 执行时机
前置通知Before 目标方法执行前执行
后置通知After 目标方法执行后执行(无论是否抛出异常)
返回通知AfterReturning 目标方法正常返回后执行
异常通知AfterThrowing 目标方法抛出异常后执行
环绕通知Around 包裹目标方法执行,可自定义目标方法执行前后的逻辑(功能最强)

二、原理

Spring AOP 基于动态代理实现,分为两种方式:

  1. JDK 动态代理
    通过 java.lang.reflect.Proxy 生成代理类,代理类实现目标接口,调用 InvocationHandler 处理横切逻辑;基于接口,无侵入。

  2. CGLIB 动态代理
    通过继承目标类生成子类作为代理类,重写目标方法,织入横切逻辑;基于继承,目标类不能是 final方法不能是 final。

Spring AOP 自动选择规则:如果目标类实现了接口,使用 JDK 动态代理;否则使用 CGLIB。

三、实战

  例如存在以下业务模块:

@Service
public class OrderService {

    // 模拟创建订单
    public String createOrder(String orderNo) {
        System.out.println("创建订单:" + orderNo);
        return "订单创建成功:" + orderNo;
    }

    // 模拟删除订单(故意抛出异常)
    public void deleteOrder(String orderNo) {
        System.out.println("删除订单:" + orderNo);
        throw new RuntimeException("删除订单失败:订单不存在");
    }
}

AOP实现

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

// 1. 标记为切面类
@Aspect
// 2. 交给Spring容器管理
@Component
public class LogAspect {

    // 3. 定义切点匹配OrderService类的所有方法
    @Pointcut("execution(* com.example.aopdemo.service.OrderService.*(..))")
    public void orderServicePointcut() {}

    // 4. 前置通知:目标方法执行前执行
    @Before("orderServicePointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        // 获取方法名和参数
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("[前置通知] 方法:" + methodName + ",参数:" + Arrays.toString(args));
    }

    // 5. 后置通知:目标方法执行后执行(无论是否异常)
    @After("orderServicePointcut()")
    public void afterAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[后置通知] 方法:" + methodName + " 执行完毕");
    }

    // 6. 返回通知:目标方法正常返回后执行
    @AfterReturning(value = "orderServicePointcut()", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[返回通知] 方法:" + methodName + ",返回值:" + result);
    }

    // 7. 异常通知:目标方法抛出异常后执行
    @AfterThrowing(value = "orderServicePointcut()", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[异常通知] 方法:" + methodName + ",异常:" + ex.getMessage());
    }

    // 8. 环绕通知:包裹目标方法执行(功能最强)
    @Around("orderServicePointcut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String methodName = proceedingJoinPoint.getSignature().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        
        // 前置逻辑
        System.out.println("[环绕通知-前置] 方法:" + methodName + ",参数:" + Arrays.toString(args));
        
        Object result = null;
        try {
            // 执行目标方法
            result = proceedingJoinPoint.proceed();
            // 返回后逻辑
            System.out.println("[环绕通知-返回] 方法:" + methodName + ",返回值:" + result);
        } catch (Throwable e) {
            // 异常逻辑
            System.out.println("[环绕通知-异常] 方法:" + methodName + ",异常:" + e.getMessage());
            throw e; // 必须抛出异常,否则上层无法捕获
        } finally {
            // 后置逻辑
            System.out.println("[环绕通知-后置] 方法:" + methodName + " 执行完毕");
        }
        return result;
    }
}

  测试:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    // 测试正常方法createOrder
    @Test
    public void testCreateOrder() {
        orderService.createOrder("ORDER_20251225");
    }

    // 测试异常方法deleteOrder
    @Test
    public void testDeleteOrder() {
        try {
            orderService.deleteOrder("ORDER_9999");
        } catch (Exception e) {
            // 捕获异常,避免测试中断
        }
    }
}

  测试结果:

[环绕通知-前置] 方法createOrder参数[ORDER_20251225]
[前置通知] 方法createOrder参数[ORDER_20251225]
创建订单ORDER_20251225
[环绕通知-返回] 方法createOrder返回值订单创建成功ORDER_20251225
[返回通知] 方法createOrder返回值订单创建成功ORDER_20251225
[后置通知] 方法createOrder 执行完毕
[环绕通知-后置] 方法createOrder 执行完毕


[环绕通知-前置] 方法deleteOrder参数[ORDER_9999]
[前置通知] 方法deleteOrder参数[ORDER_9999]
删除订单ORDER_9999
[环绕通知-异常] 方法deleteOrder异常删除订单失败订单不存在
[异常通知] 方法deleteOrder异常删除订单失败订单不存在
[后置通知] 方法deleteOrder 执行完毕
[环绕通知-后置] 方法deleteOrder 执行完毕

四、切点表达式

切点表达式 说明
execution(* com.example.service.*.*(..)) 匹配 com.example.service 包下所有类的所有方法
execution(public String com.example.service.OrderService.create*(String)) 匹配 OrderService 类中公共、返回值为 String、方法名以 create 开头、参数为 String 的方法
execution(* com.example..*Service.*(..)) 匹配 com.example 包及其子包下所有以 Service 结尾的类的所有方法
@annotation(com.example.annotation.Log) 匹配标注了 @Log 自定义注解的方法

  组合切点:

// 匹配OrderService的方法 且 方法名包含"Order"
@Pointcut("execution(* com.example.service.OrderService.*(..)) && execution(* *Order*(..))")
public void combinePointcut() {}

五、总结

通俗的解释AOP本质是找目标通过切点规则精准找到要 “加戏” 的方法 / 类)→ 造代理Spring 自动给这些目标生成一个 “中间商”(代理对象))→ 加戏(在目标方法真正执行前 / 后 / 出问题时,让这个 “中间商” 帮你干额外的活)。

::: tip 基于 Spring AOP 的知名框架/组件汇总

框架/组件 核心作用 Spring AOP 应用说明
AspectJ 提供切点表达式语法、注解解析(@Aspect/@Pointcut 等) Spring AOP 仅复用其语法规则,底层仍为自身动态代理(未使用 AspectJ 编译期织入)
Spring Core 容器管理、动态代理创建JDK/CGLIB 负责切面/目标类的生命周期管理,生成代理对象并完成织入逻辑
Spring Transaction 声明式事务管理 切点:匹配 @Transactional 注解方法;
通知:方法执行前开启事务、执行后提交、异常时回滚
Spring Cache 声明式缓存管理 切点:匹配 @Cacheable/@CacheEvict 等注解方法;
通知:方法执行前查缓存、执行后更新缓存
spring-boot-starter-aop AOP 基础依赖封装 自动引入 AspectJ + Spring AOP 核心,开箱即用
Spring Security 安全权限管理 切点:匹配 @PreAuthorize 等权限注解方法;
通知:方法执行前校验用户权限,无权限抛异常
MyBatis-Plus 增强 MyBatis CRUD 操作 切点:匹配 Mapper 接口方法/标注注解的实体类;
通知:织入分页逻辑、注解(@TableName解析等
Shiro 权限管理框架 切点:匹配 @RequiresPermissions 注解方法;
通知:方法执行前校验用户权限
Seata 分布式事务框架 切点:匹配 @GlobalTransactional 注解方法;
通知:织入全局事务的开启、提交、回滚逻辑
Sentinel 流量控制/熔断降级框架 切点:匹配 @SentinelResource 注解方法;
通知:方法执行前校验流量阈值、熔断规则
Dynamic-TP 动态线程池框架 切点:匹配线程池执行方法;
通知:织入线程池耗时、队列长度等监控逻辑
p6spy 数据库 SQL 监控 切点:拦截 JDBC 操作方法;
通知:织入 SQL 打印、执行耗时统计逻辑
dynamic-datasource-spring-boot-starter 动态数据源切换 切点:匹配 @DS 注解方法;
通知:方法执行前切换数据源、执行后恢复默认数据源
xxl-job 分布式任务调度 切点:匹配任务执行方法;
通知:织入任务执行日志、监控指标采集逻辑
Spring Cloud Sleuth 微服务链路追踪 切点:拦截 HTTP/RPC 调用方法;
通知:织入 traceId/spanId 传递、链路信息采集逻辑
:::