Spring AOP 详细讲解

Spring AOP 全面详解


一、AOP 概述(什么是 AOP)

AOP(Aspect Oriented Programming,面向切面编程)是一种软件开发思想,目的是让开发者能够将程序中的横切关注点(cross-cutting concerns)从核心业务逻辑中抽离出来,使代码更加简洁、可复用、可维护。

横切关注点是什么?

横切关注点不是业务本身,但却伴随业务逻辑:

  • 日志记录(Log)
  • 安全认证(Authentication)
  • 权限校验(Authorization)
  • 缓存(Cache)
  • 事务控制(Transaction)
  • 监控(Metrics)
  • 异常处理(Exception Handling)

这些逻辑往往会散落在业务方法的前后,导致:

  • 代码重复
  • 难以维护
  • 逻辑耦合严重

AOP 的目标是:

  • 将横切逻辑集中管理
  • 不修改原代码的前提下增强功能(OOP 难实现)
  • 提高模块化能力

在 Spring 中,AOP 被大量用于:

  • 事务管理(@Transactional)
  • 日志增强
  • 权限控制
  • 缓存 @Cacheable
  • 注解驱动开发(自定义注解 + AOP)

二、AOP 与 OOP 区别

思想 描述
OOP(面向对象) 关注业务模型的抽象与封装,如用户、订单、购物车。
AOP(面向切面) 关注跨业务的系统性功能,动态插入方法执行前后。

两者互补而非替代关系。


三、Spring AOP 的底层原理

Spring AOP 采用动态代理机制实现,而不是修改字节码(AspectJ 才修改字节码)。

核心结论:

Spring AOP 只支持 方法级别增强(Method-level AOP)。

Spring AOP 动态代理实现方式

类型 使用场景 底层机制
JDK 动态代理 目标对象有接口 通过接口生成代理对象
CGLIB 动态代理 目标类无接口 通过继承 + 覆写方法生成代理对象

Spring 5.0 后默认优先选择:

  • 若类实现了接口 → JDK 动态代理
  • 否则 → CGLIB

Spring Boot 默认开启 CGLIB 代理:
spring.aop.proxy-target-class=true(默认开启,为兼容 @Transactional 等)


四、AOP 的核心概念(重点记忆)

Spring AOP 中有六个必须掌握的术语:


1. JoinPoint(连接点)

能被 AOP 拦截的方法执行点。

Spring AOP 仅支持:

  • 方法执行(Method Execution)

2. Pointcut(切点)

决定哪些 JoinPoint 会被拦截。

常用表达式:

java 复制代码
execution(* com.example.service.*.*(..))

3. Advice(通知)

切面在特定时机执行的增强逻辑。

类型:

  • 前置通知(@Before)
  • 后置通知(@After)
  • 返回通知(@AfterReturning)
  • 异常通知(@AfterThrowing)
  • 环绕通知(@Around)------最强,能控制方法执行

4. Aspect(切面)

切点 + 通知的组合体。

使用 @Aspect 注解声明。


5. Target(目标对象)

被增强的对象,即业务类。


6. Proxy(代理对象)

实际运行的是代理对象,而不是目标对象。


理解关系图:

复制代码
Aspect = Pointcut + Advice
Pointcut → 定位方法
Advice   → 定义增强逻辑
Target   → 原始对象
Proxy    → 被增强后的对象
JoinPoint→ 方法执行时刻(可被增强)

五、AOP 注解开发核心示例

下面给出完整的 Spring AOP 注解方式示例。


1. 引入依赖(Spring Boot 自动包含)

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 定义切面类

java 复制代码
@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}

    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("方法执行前:" + joinPoint.getSignature());
    }

    @After("servicePointcut()")
    public void after() {
        System.out.println("方法执行后");
    }

    @AfterReturning(value = "servicePointcut()", returning = "result")
    public void afterReturning(Object result) {
        System.out.println("返回结果:" + result);
    }

    @AfterThrowing(value = "servicePointcut()", throwing = "e")
    public void afterThrowing(Exception e) {
        System.out.println("发生异常:" + e.getMessage());
    }

    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        Object result = pjp.proceed();
        System.out.println("环绕后");
        return result;
    }
}

六、切点表达式详解(execution 语法)

execution 表达式结构

复制代码
execution(modifiers-pattern? return-type-pattern 
          declaring-type-pattern? name-pattern(param-pattern)
          throws-pattern?)

常用写法(必须记住)

1. 匹配某包下所有方法:

java 复制代码
execution(* com.example.service.*.*(..))

2. 匹配某类所有方法

java 复制代码
execution(* com.example.service.UserService.*(..))

3. 匹配带注解的方法

java 复制代码
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")

七、AOP 中 @Around 的深度讲解(核心)

环绕通知是最强的 Advice,可以:

  • 控制方法是否执行
  • 修改参数
  • 修改返回值
  • 捕获异常
  • 统计耗时
  • 事务控制(@Transactional 就是基于环绕 + 动态代理)

示例:

java 复制代码
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    System.out.println("---------进入方法---------");

    Object result = pjp.proceed();  // 执行目标方法

    long time = System.currentTimeMillis() - start;
    System.out.println("---------执行耗时:" + time + "ms---------");
    return result;
}

八、AspectJ 与 Spring AOP 区别

1. Spring AOP

  • 基于代理
  • 方法级别
  • 运行时增强
  • 不修改 class 字节码

2. AspectJ

  • 编译时增强(compile-time weaving)
  • 类级别、字段级别、构造器级别增强
  • 功能远强于 Spring AOP
  • 需要 AJ 编译器
  • 企业开发一般使用 Spring AOP,不使用 AspectJ 编译器

九、Spring AOP 工作流程(面试常问)

核心流程(JDK 动态代理举例):

  1. IOC 容器启动,扫描到 @Aspect
  2. Spring 解析切面,生成切点表达式
  3. 使用 Advisor、MethodMatcher 组合增强器
  4. 在创建 Bean 时判断是否匹配切点
  5. 如果匹配,则创建代理对象(Proxy)
  6. 调用业务方法时,会先进入代理逻辑,再执行通知,最后执行目标方法

示意图:

复制代码
Client
   ↓
Proxy(增强逻辑)
   ↓
Target(真实业务对象)

十、自定义注解 + AOP(高级开发必会)

例如自定义一个 @Log 注解:

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

切面:

java 复制代码
@Aspect
@Component
public class LogAspect {

    @Pointcut("@annotation(com.example.annotation.Log)")
    public void logPointcut() {}

    @Around("logPointcut()")
    public Object handler(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Log log = method.getAnnotation(Log.class);
        System.out.println("开始记录日志:" + log.value());

        return pjp.proceed();
    }
}

十一、实战案例:接口日志、监控、异常统一处理

如下需求在企业中非常常见:

  • 记录接口入参
  • 记录出参
  • 记录执行耗时
  • 记录异常

环绕通知即可实现:

java 复制代码
@Around("@annotation(com.example.annotation.LogApi)")
public Object logApi(ProceedingJoinPoint pjp) throws Throwable {

    long start = System.currentTimeMillis();

    Object[] params = pjp.getArgs();
    System.out.println("入参:" + Arrays.toString(params));

    Object result;
    try {
        result = pjp.proceed();
    } catch (Throwable e) {
        System.out.println("异常:" + e.getMessage());
        throw e;
    }

    System.out.println("出参:" + result);
    System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");

    return result;
}

十二、AOP 的常见问题与陷阱(非常重要)


问题 1:为什么 AOP 不能增强同类内部方法调用?

因为 Spring AOP 是基于 proxy 的,当对象内部调用自己的方法时,是"this.方法",不经过代理。

解决方案:

  • 使用 AopContext.currentProxy()
  • 或使用 AspectJ compile-time weaving(CTW)

问题 2:私有方法能被 AOP 拦截吗?

不能,因为动态代理无法代理 private 方法。


问题 3:为什么 @Transactional 对方法级别有效?

因为事务是通过 "环绕通知" + "代理" 实现的。


问题 4:构造方法能增强吗?

Spring AOP 不支持。

AspectJ 可以增强构造器与字段。


十三、AOP 在企业开发中的典型场景

场景 描述
日志记录 记录方法名、参数、返回值、耗时
事务控制 @Transactional
缓存处理 @Cacheable、@CacheEvict
接口限流 结合 Redis 实现
权限校验 @PreAuthorize
数据脱敏 返回前替换手机号等敏感字段
API 访问统计 统计接口调用次数、耗时
请求幂等性 避免重复提交

十四、与 Spring MVC 配合使用

在 Controller 层 logging 示例:

java 复制代码
@Around("execution(* com.example.controller.*.*(..))")
public Object controllerLog(ProceedingJoinPoint pjp) throws Throwable {
    HttpServletRequest request = ((ServletRequestAttributes) 
        RequestContextHolder.getRequestAttributes()).getRequest();

    String url = request.getRequestURI();
    String ip = request.getRemoteAddr();

    System.out.println("访问 URL: " + url);
    System.out.println("来源 IP: " + ip);

    return pjp.proceed();
}

十五、Spring AOP 设计理念总结(非常关键)

Spring AOP 的设计基于:

  1. 遵循 Spring 轻量级、无侵入、可扩展理念
  2. 不使用编译时 weaving,而是运行时代理
  3. 与 IOC 深度集成
  4. 利用动态代理完成增强,不改变业务代码
  5. 事务、缓存等核心模块全部基于 AOP

十六、AOP 学习路线建议

建议学习顺序:

  1. 理解代理模式(JDK / CGLIB)
  2. 掌握 AOP 的核心概念(JoinPoint / Advice / Pointcut)
  3. 学习 execution 表达式
  4. 学会自定义注解 + AOP
  5. 通过真实业务需求(日志、监控、权限)练习
  6. 源码级学习 AOP 实现原理

十七、完整总结(面试可背)

  • Spring AOP 是一种基于代理的运行时增强技术
  • 支持方法级别的横切逻辑注入
  • 动态代理实现:JDK / CGLIB
  • 切点表达式基于 execution
  • 通知类型包括 Before、After、Around 等
  • @Around 最强,可控制方法执行
  • @Transactional、缓存、日志等都是 AOP 实现
  • 内部方法调用无法被 AOP 增强
  • 私有方法无法增强
  • Spring AOP 不修改字节码,而 AspectJ 可以

相关推荐
不吃香菜学java7 小时前
spring-依赖注入
java·spring boot·后端·spring·ssm
南部余额7 小时前
Spring Boot 整合 MinIO:封装常用工具类简化文件上传、启动项目初始化桶
java·spring boot·后端·文件上传·工具类·minio·minioutils
海南java第二人7 小时前
Spring Bean生命周期深度剖析:从创建到销毁的完整旅程
java·后端·spring
QQ19632884757 小时前
ssm基于Springboot+的球鞋销售商城网站vue
vue.js·spring boot·后端
逑之7 小时前
C语言笔记5:函数
java·c语言·笔记
JavaLearnerZGQ7 小时前
1、Java中的线程
java·开发语言·python
小当家.1058 小时前
深入理解JVM:架构、原理与调优实战
java·jvm·架构
幽络源小助理8 小时前
springboot校园车辆管理系统源码 – SpringBoot+Vue项目免费下载 | 幽络源
vue.js·spring boot·后端
刀法如飞8 小时前
一款开箱即用的Spring Boot 4 DDD工程脚手架
java·后端·架构