Spring AOP

AOP概述

什么是AOP

AOP(Aspect-oriented Programming) 是面向切面编程,它补充了OOP(面向对象编程)的不足:

特性OOPAOP
基本单元类(Class)切面(Aspect)
关注点纵向业务逻辑横向公共逻辑
解决问题业务模块化横切关注点分离

为什么需要AOP

想象一个场景:多个Service方法都需要记录日志

// 没有AOP时 - 日志代码分散各处
public class UserServiceImpl {
    public void createUser(String name) {
        System.out.println("[日志] 方法开始");  // 重复代码
        // 业务逻辑
        System.out.println("[日志] 方法结束");  // 重复代码
    }
}

public class OrderServiceImpl {
    public void createOrder(String product) {
        System.out.println("[日志] 方法开始");  // 重复代码
        // 业务逻辑
        System.out.println("[日志] 方法结束");  // 重复代码
    }
}

问题

  • ❌ 日志代码与业务逻辑强耦合
  • ❌ 代码重复,无法复用
  • ❌ 修改日志逻辑需要改动多处

AOP解决方案:将日志逻辑封装成切面,自动应用到目标方法

多个业务方法共享统一切面逻辑的结构图
多个业务方法共享统一切面逻辑的结构图

切面要素

image-20260503025308652
image-20260503025308652

AOP 真正要解决的问题,不是重新写业务方法,而是明确“哪些组件需要被增强、在方法执行的什么阶段做什么事”。所以一个切面至少要回答两个问题

  1. 增强范围怎么圈定
  2. 增强动作如何执行。

定义一个切面需要两个核心要素:

要素说明对应概念
What + When封装什么逻辑,什么时候执行Advice(通知)
Where对哪些类的哪些方法生效Pointcut(切入点)

AOP 的底层支撑就是动态代理。Spring 容器负责管理 Bean,AOP 的做法是在合适的 Bean 外层包上一层代理对象,让公共逻辑在不改业务代码的前提下被统一织入。

其中,“哪些组件、哪些方法需要增强”对应 Where;“增强逻辑做什么、在什么时候执行”对应 What + When。可以把它理解成:一个负责圈定范围,一个负责安排增强动作。

小结

本章核心概念:AOP 的价值不在于“多学一个新名词”,而在于把日志、事务、权限这类横切逻辑从业务代码中抽离出来,让系统更容易复用和维护。

AOP核心术语

术语英文说明
目标类Target需要被代理的原始类
代理类Proxy动态代理生成的类
连接点JoinPoint可以被增强的方法
切入点PointCut定义哪些连接点会被增强
通知Advice具体的增强逻辑
切面Aspect切入点 + 通知的模块化表示
织入Weaving将Advice应用到目标对象的过程
切面由切入点和通知组成的结构图
切面由切入点和通知组成的结构图

核心概念:AOP 的术语并不是零散记忆的名词表,它们围绕“目标对象被代理后,增强逻辑如何命中并执行”这一条链路组织起来。

Spring AOP底层原理

动态代理实现

Spring AOP在运行期通过代理方式向目标类织入增强代码,核心原理是动态代理

// 代理类示意(非真实代码)
public class UserServiceProxy implements UserService {
    private UserService target;  // 目标对象
    private LoggerAdvice advice; // 增强逻辑

    public void createUser(String name) {
        advice.before();                    // 前置增强
        target.createUser(name);            // 调用目标方法
        advice.after();                     // 后置增强
    }
}

3.2 两种代理方式

代理方式适用场景特点
JDK动态代理目标类实现了接口基于接口生成代理
CGLIB代理目标类没有接口基于子类生成代理
JDK动态代理:           CGLIB代理:
┌──────────────┐       ┌──────────────┐
│  接口         │       │   目标类      │
│  UserService  │       │ UserService  │
└──────┬───────┘       └──────┬───────┘
       │                      │
       ↓                      ↓
┌──────────────┐       ┌──────────────┐
│   代理类      │       │   子类(代理)  │
│ implements   │       │ extends      │
│ UserService  │       │ UserService  │
└──────────────┘       └──────────────┘
image-20260503025514204

JDK 动态代理的核心是 implements 接口,而 CGLIB 代理的核心是 extends 目标类。这张图的重点在于搞清楚代理对象和原始对象之间到底是“实现同一接口”还是“继承原始类型”的关系。

核心概念:Spring AOP 不是修改原始类源码,而是在运行期生成代理对象,把增强逻辑包进方法调用流程里。

AOP快速入门

添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.2.15</version>
</dependency>

<!-- AspectJ依赖(Spring AOP使用了AspectJ的切点表达式) -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
</dependency>

<!-- 测试验证需要的Spring Test与JUnit Jupiter -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.2.15</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

开启AOP功能

切面组件

  • 增加AspectJ的注解开关 → 配置类上增加一个注解@EnableAspectJAutoProxy
  • 把组件标记为切面组件 → @Aspect
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy  // 开启Spring AOP
public class SpringConfiguration {
}

定义切面

@Component  // 注册为Spring Bean
@Aspect     // 标记为切面类
public class LoggingAspect {

    // 定义切入点:匹配com.example.service包下所有类的所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointCut() {}

    // 前置通知:方法执行前执行
    @Before("servicePointCut()")
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("[日志] 开始执行: " + joinPoint.getSignature().getName());
    }

    // 后置通知:方法执行后执行
    @After("servicePointCut()")
    public void afterLog(JoinPoint joinPoint) {
        System.out.println("[日志] 执行结束: " + joinPoint.getSignature().getName());
    }
}

效果验证

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AopTest {

    @Autowired
    private UserService userService;

    @Test
    public void testAop() {
        userService.createUser("张三");
        // 输出:
        // [日志] 开始执行: createUser
        // 业务逻辑:创建用户
        // [日志] 执行结束: createUser
    }
}

概念:AOP 的入门配置其实就是把“依赖、开关、切面类、切入点、通知方法”这几块拼起来,让 Spring 知道该在什么位置织入什么逻辑。

切入点表达式(PointCut)

execution表达式

最常用,用于直接匹配方法签名。

语法

execution(访问修饰符 返回值 包名.类名.方法名(参数列表))

通配符

  • *:匹配任意字符(单个)
  • ..:匹配任意字符(多个)

示例

表达式含义
execution(public (..))任意public方法
execution( set(..))任意以set开头的方法
execution( com.service..*(..))service包下任意类的任意方法
execution( com.service...*(..))service包及其子包下任意类的任意方法
execution( com.service.UserService.(..))UserService接口的所有方法
execution( com.service..create*(..))service包下任意类以create开头的方法
execution(String com.service..find(..))返回值为String且以find开头的方法
execution( com.service..(String, ))第一个参数为String,第二个为任意类型

@annotation表达式

匹配标注了指定注解的方法。

// 1. 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogOperation {
}

// 2. 使用注解
@Service
public class UserServiceImpl {
    @LogOperation
    public void createUser(String name) {
        // ...
    }
}

// 3. 定义切入点
@Pointcut("@annotation(com.example.annotation.LogOperation)")
public void logPointCut() {}

@target表达式

匹配目标对象类上标注了指定注解的方法(类级别的注解)。

// 1. 定义类级别注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ServiceLog {
}

// 2. 标注在类上
@Service
@ServiceLog
public class UserServiceImpl {
    public void createUser(String name) { }  // 会被匹配
    public void deleteUser(String name) { }  // 也会被匹配
}

// 3. 定义切入点
@Pointcut("@target(com.example.annotation.ServiceLog)")
public void serviceLogPointCut() {}

表达式对比

表达式匹配粒度使用场景
execution方法签名按包、类、方法名匹配
@annotation方法注解精确控制哪些方法需要增强
@target类注解对整个类的所有方法增强

核心概念:切入点表达式的关键不是死记语法,而是根据业务需要选择“按方法签名拦截”还是“按注解拦截”。

通知类型(Advice)

5种通知类型

通知类型注解执行时机特点
前置通知@Before方法执行前无法阻止方法执行
后置通知@AfterReturning方法正常返回后可获取返回值
异常通知@AfterThrowing方法抛出异常后可获取异常对象
最终通知@After方法执行结束后无论正常或异常
环绕通知@Around方法执行前后最强控制力,可控制是否执行目标方法

执行顺序

正常执行流程:
@Before → 目标方法 → @AfterReturning → @After

异常执行流程:
@Before → 目标方法(抛出异常) → @AfterThrowing → @After

各类通知详解

@Before(前置通知)

@Before("servicePointCut()")
public void beforeAdvice(JoinPoint joinPoint) {
    // 获取方法签名
    String methodName = joinPoint.getSignature().getName();
    // 获取参数
    Object[] args = joinPoint.getArgs();
    // 获取目标对象
    Object target = joinPoint.getTarget();

    System.out.println("[前置通知] 准备执行: " + methodName);
}

@AfterReturning(返回通知)

@AfterReturning(
    value = "servicePointCut()",
    returning = "result"  // 绑定返回值到参数
)
public void afterReturning(JoinPoint joinPoint, Object result) {
    System.out.println("[返回通知] 返回值: " + result);
}

@AfterThrowing(异常通知)

@AfterThrowing(
    value = "servicePointCut()",
    throwing = "ex"  // 绑定异常到参数
)
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
    System.out.println("[异常通知] 发生异常: " + ex.getMessage());
}

@After(最终通知)

@After("servicePointCut()")
public void afterAdvice(JoinPoint joinPoint) {
    System.out.println("[最终通知] 方法执行结束");
}

@Around(环绕通知)

最强大的通知类型,可以控制目标方法是否执行

@Around("servicePointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    // 1. 前置逻辑
    System.out.println("[环绕通知-前]");

    // 2. 获取信息
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();

    Object result = null;
    try {
        // 3. 执行目标方法(如果不调用proceed,目标方法不会执行)
        result = joinPoint.proceed(args);

        // 4. 后置逻辑(正常返回)
        System.out.println("[环绕通知-后] 返回值: " + result);
    } catch (Throwable e) {
        // 5. 异常处理
        System.out.println("[环绕通知-异常] " + e.getMessage());
        throw e;
    } finally {
        // 6. 最终逻辑
        System.out.println("[环绕通知-最终]");
    }

    return result;
}

通知选择指南


  记录日志/权限检查(不关心结果)      → @Before   
  记录返回值/结果处理                 → @AfterReturning 
  异常处理/告警                       → @AfterThrowing  
  资源清理(必须执行)                → @After    
  事务控制/性能监控(完全控制)       → @Around   

核心概念:不同通知类型的差异,本质上是“它们能介入目标方法执行流程的哪个阶段、能拿到哪些信息、能控制到什么程度”。

JoinPoint与ProceedingJoinPoint

JoinPoint API

public interface JoinPoint {
    // 获取方法签名(包含方法名、参数类型等)
    Signature getSignature();

    // 获取目标对象(被代理的原始对象)
    Object getTarget();

    // 获取代理对象
    Object getThis();

    // 获取方法参数
    Object[] getArgs();
}

ProceedingJoinPoint(环绕通知专用)

public interface ProceedingJoinPoint extends JoinPoint {
    // 执行目标方法
    Object proceed() throws Throwable;

    // 带参数执行目标方法
    Object proceed(Object[] args) throws Throwable;
}

使用示例

@Around("servicePointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    // 方法信息
    String className = joinPoint.getTarget().getClass().getSimpleName();
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();

    // 记录入参
    System.out.println("[" + className + "." + methodName + "] 入参: " + Arrays.toString(args));

    long startTime = System.currentTimeMillis();

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

    long endTime = System.currentTimeMillis();

    // 记录出参和执行时间
    System.out.println("[" + className + "." + methodName + "] 出参: " + result + " 耗时: " + (endTime - startTime) + "ms");

    return result;
}

核心概念:JoinPoint 负责提供方法执行时的上下文,ProceedingJoinPoint 则在此基础上进一步提供了对目标方法执行过程的控制能力。

核心注解速查表

注解作用使用位置
@EnableAspectJAutoProxy开启AOP功能配置类
@Aspect标记切面类
@Pointcut定义切入点方法
@Before前置通知方法
@AfterReturning返回通知方法
@AfterThrowing异常通知方法
@After最终通知方法
@Around环绕通知方法

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇