背景

本文是《Java 后端从小白到大神》修仙系列之框架学习,Java框架之Spring框架第二篇。本篇文章主要聊Java框架,那么必然从Spring框架开始,可以说Spring框架是Java企业级开发的基石,若想详细学习请点击首篇博文,我们现在开始吧。

文章概览

  1. 面向切面编程 (AOP)

Spring框架

面向切面编程 (AOP)

1. AOP 基本概念

让我们先从一个案例说起,用户登录系统:

  • 希望所有登录相关的方法,都统一加日志记录
  • 又希望某些敏感操作,要统一加权限校验
graph TD
    subgraph 用户登录系统
        A[UserController] --> B[UserService]
        B --> C[login]
        B --> D[logout]
        B --> E[adminOperation]
    end
    
    subgraph AOP实施过程
        F[日志切面 LoggingAspect] --> G[切点1 @Pointcut execution * login * ..]
        F --> H[通知1 @Before logMethodCall]
        
        I[权限切面 SecurityAspect] --> J[切点2 @Pointcut execution * admin * ..]
        I --> K[通知2 @Before checkPermission]
        
        C -->|连接点1 JoinPoint| G
        E -->|连接点2 JoinPoint| J
        
        G -->|织入 Weaving| H
        J -->|织入 Weaving| K
    end
    
    style F fill:#f9f,stroke:#333
    style I fill:#6f9,stroke:#333
    style G fill:#ff6,stroke:#333
    style J fill:#ff6,stroke:#333
    style H fill:#6cf,stroke:#333
    style K fill:#6cf,stroke:#333
    style C fill:#cdf,stroke:#333
    style E fill:#cdf,stroke:#333

2. 场景说明与术语对应

  1. 切面 (Aspect) - 功能模块

    • 日志切面 (LoggingAspect):粉紫色模块
    • 权限切面 (SecurityAspect):绿色模块

    如同保安小队,每个切面负责特定功能

  2. 切点 (Pointcut) - 选择规则

    • 切点1:黄色模块 - "execution(* login*(..))"

      选择所有以login开头的方法

    • 切点2:黄色模块 - "execution(* admin*(..))"

      选择所有以admin开头的方法

    如同保安队长的指令:“只检查VIP入口”

  3. 连接点 (JoinPoint) - 可插入位置

    • login():蓝色方法 - 连接点1
    • adminOperation():蓝色方法 - 连接点2

    如同大楼的入口,每个方法调用点都是潜在切入点

  4. 通知 (Advice) - 具体动作

    • logMethodCall():天蓝色模块 - 前置通知

      在login*方法执行前记录日志

    • checkPermission():天蓝色模块 - 前置通知

      在admin*方法执行前检查权限

    如同保安的具体动作:登记或检查证件

  5. 织入 (Weaving) - 植入过程

    • 切点1 → 通知1 的橙色箭头
    • 切点2 → 通知2 的橙色箭头

    如同保安实际到达岗位执勤

3. 使用场景决策指南

需求场景 AOP组件选择 配置示例 说明
需要添加新功能模块 切面(Aspect) @Aspect public class XxxAspect 如日志、安全等横切关注点
需要选择特定方法 切点(Pointcut) @Pointcut("execution(* save*(..))") 用表达式定义过滤规则
需要在方法前后执行操作 通知(Advice) @Before, @After, @Around 定义执行时机和具体操作
需要获取当前方法信息 连接点(JoinPoint) log(JoinPoint jp) 可获取方法签名、参数等
需要让代理生效 织入(Weaving) 配置@EnableAspectJAutoProxy 启用AOP自动代理

4. 实际代码对应示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 切面:日志记录模块
@Aspect
public class LoggingAspect {
    // 切点:选择所有login开头的方法
    @Pointcut("execution(* login*(..))")
    public void loginMethods() {}  // 切点定义
    
    // 通知:在切点方法执行前执行
    @Before("loginMethods()")
    public void logMethodCall(JoinPoint jp) { // 连接点作为参数
        System.out.println("调用方法: " + jp.getSignature().getName());
    }
}

// 切面:权限检查模块
@Aspect
public class SecurityAspect {
    // 切点:选择所有admin开头的方法
    @Pointcut("execution(* admin*(..))")
    public void adminMethods() {}  // 切点定义
    
    // 通知:在切点方法执行前执行
    @Before("adminMethods()")
    public void checkPermission(JoinPoint jp) { // 连接点作为参数
        if (!UserContext.isAdmin()) {
            throw new SecurityException("无权限操作!");
        }
    }
}

5. 通知 (Advice) 类型

通知类型 注解 含义
前置通知 @Before 方法执行之前执行
后置通知 @After 方法执行完成后执行(不管成功/异常都会执行)
返回通知 @AfterReturning 方法成功返回后执行
异常通知 @AfterThrowing 方法抛出异常后执行
环绕通知 @Around 方法执行前后都可以控制,可以手动决定是否执行原方法 (proceed() 调用),还能控制返回值,最强大

5.1 通知类型测试代码

5种通知类型示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    // 切点表达式:拦截所有 service 包下的方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceMethods() {
    }

    // 1. 前置通知
    @Before("serviceMethods()")
    public void beforeAdvice() {
        System.out.println("【前置通知】方法即将执行!");
    }

    // 2. 后置通知
    @After("serviceMethods()")
    public void afterAdvice() {
        System.out.println("【后置通知】方法已经执行完成(无论成功失败)!");
    }

    // 3. 返回通知
    @AfterReturning("serviceMethods()")
    public void afterReturningAdvice() {
        System.out.println("【返回通知】方法成功返回!");
    }

    // 4. 异常通知
    @AfterThrowing("serviceMethods()")
    public void afterThrowingAdvice() {
        System.out.println("【异常通知】方法出现异常了!");
    }

    // 5. 环绕通知
    @Around("serviceMethods()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("【环绕通知-前】即将执行方法:" + pjp.getSignature());
        Object result = pjp.proceed(); // 执行目标方法,既原方法
        System.out.println("【环绕通知-后】方法执行完毕:" + pjp.getSignature());
        return result;
    }
}
启动测试
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Service
public class UserService {
    public void register(String username) {
        System.out.println(username + " 注册成功!");
        // throw new RuntimeException("模拟异常"); // 取消注释可以测试异常通知
    }
}

调用逻辑
userService.register("Alice");

控制台输出
环绕通知-即将执行方法void com.example.service.UserService.register(String)
前置通知方法即将执行
Alice 注册成功
后置通知方法已经执行完成无论成功失败)!
返回通知方法成功返回
环绕通知-方法执行完毕void com.example.service.UserService.register(String)

5.2 Spring AOP 通知执行顺序

graph TD
    A[外部调用目标方法] --> B{环绕通知 - 前置部分};

    B --> C[前置通知 @Before];

    C --> D[目标方法执行];

    D -- 正常返回 --> E[后置通知 @After];
    D -- 抛出异常 --> F[后置通知 @After];

    E --> G[返回通知 @AfterReturning];
    F --> H[异常通知 @AfterThrowing];

    G --> I{环绕通知 - 后置部分};
    H --> I;

    I --> J[方法执行结束];

    style A fill:#f9f,stroke:#333,stroke-width:2px;
    style B fill:#ccf,stroke:#333,stroke-width:2px;
    style C fill:#9cf,stroke:#333,stroke-width:2px;
    style D fill:#aaf,stroke:#333,stroke-width:2px;
    style E fill:#ddf,stroke:#333,stroke-width:2px;
    style F fill:#ddf,stroke:#333,stroke-width:2px;
    style G fill:#9fd,stroke:#333,stroke-width:2px;
    style H fill:#f99,stroke:#333,stroke-width:2px;
    style I fill:#ccf,stroke:#333,stroke-width:2px;
    style J fill:#f9f,stroke:#333,stroke-width:2px;

6. 切点表达式 (Pointcut Expressions)

  • execution:是切入点指示符,用于匹配方法执行的连接点。

    • 语法:execution([修饰符] 返回值类型 [包名.]类名.方法名(参数列表))
    • 示例: execution(public * com.example.service.UserService.*(..)),表示匹配 com.example.service.UserService 类中所有 public 方法,第一*表示返回值为任意类型,第二*表示任意方法..表示参数任意
    • 示例: execution(* com.example..*Service.find*(..)),匹配 com.example 包及子包下所有名字以Service结尾的类,且类中以 find 开头的方法,返回值任意
  • within:within匹配某类或包下面的方法

    • 语法: within(完整类名或包名..)
    • 示例: within(com.example.service.UserService),匹配 UserService 类里的所有方法
    • 示例: within(com.example.service..*),匹配 com.example.service 包及其子包下所有类的所有方法
  • this/target:this 和 target(代理对象 or 目标对象类型)

    • 语法: this(A):当前代理对象是 A 类型,target(A):当前目标对象是 A 类型
    • 示例: this(com.example.service.UserService),如果是用 接口代理(JDK动态代理),需要注意接口与实现类的区别
    • 示例: target(com.example.service.impl.UserServiceImpl),无论代理还是目标,最终落到 UserServiceImpl 实现类上,都匹配。
    • CGLIB代理时this和target基本一致,JDK代理时不同
  • args:匹配方法参数类型。

    • 语法:args(参数类型列表)
    • 示例:args(java.lang.String),匹配只有一个参数,且是 String 类型的方法
    • 示例:args(java.lang.String, ..),匹配第一个参数是 String,后面可以有任意多个参数的方法
  • @annotation:匹配方法上有某个注解。

    • 语法:@annotation(注解类名)
    • 示例:@annotation(org.springframework.transaction.annotation.Transactional),匹配被 @Transactional 标注的方法

示例:拦截com.example.service 包下的所有 *Service 类,方法名以 find 开头,并且方法上标了自定义注解 @MyLog,在方法执行前打印日志。

1. 创建注解 @MyLog
1
2
3
4
5
6
7
8
package com.example.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
2. 创建一个业务类 UserService
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.service;

import com.example.annotation.MyLog;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @MyLog
    public String findUserById(String id) {
        System.out.println("执行 findUserById 逻辑,参数:" + id);
        return "User:" + id;
    }

    public String updateUser(String id) {
        System.out.println("执行 updateUser 逻辑,参数:" + id);
        return "Updated User:" + id;
    }
}

注意
- `findUserById`  `@MyLog`
- `updateUser` 没有 `@MyLog`
3. 创建切面类 LogAspect
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.example.aspect;

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

@Aspect
@Component
public class LogAspect {

    // execution(* com.example.service.*Service.find*(..)) , ➔ 包 + 类名 + 方法名规则匹配。
    // @annotation(com.example.annotation.MyLog) , ➔ 方法必须标注了 @MyLog 注解。
    // 两者 && 联合生效
    @Before("execution(* com.example.service.*Service.find*(..)) && @annotation(com.example.annotation.MyLog)")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("[日志切面] 方法调用前:" + joinPoint.getSignature());
    }
}
4. 启动测试
  1. Spring Boot 配置启用 AOP application.properties: spring.aop.proxy-target-class=true 默认开启了 AOP,不需要特别额外操作。 如果是纯 Spring 项目,需要加:@EnableAspectJAutoProxy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Autowired
private UserService userService;

@Test
public void testFindUser() {
    userService.findUserById("123");
    userService.updateUser("456");
}

[日志切面] 方法调用前String com.example.service.UserService.findUserById(String)
执行 findUserById 逻辑参数123
执行 updateUser 逻辑参数456

示例:拦截任意 service 方法,方法执行前后都可以插入逻辑,可以拿到参数、可以改返回值、可以统一异常处理!

1. 先定义一个注解:@Monitor
1
2
3
4
5
6
7
8
9
package com.example.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {
    String value() default "";
}
2. 写业务方法:OrderService
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.service;

import com.example.annotation.Monitor;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Monitor("下单接口")
    public String createOrder(String productId) {
        System.out.println("执行 createOrder,商品ID:" + productId);
        return "订单号: " + System.currentTimeMillis();
    }

    public String cancelOrder(String orderId) {
        System.out.println("执行 cancelOrder,订单ID:" + orderId);
        return "取消成功";
    }
}
3. 创建一个超强 Around 切面
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.example.aspect;

import com.example.annotation.Monitor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MonitorAspect {

    @Around("@annotation(monitor)")
    public Object around(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
        String methodName = pjp.getSignature().toShortString();
        Object[] args = pjp.getArgs();
        long start = System.currentTimeMillis();

        System.out.println("[Monitor] 开始调用方法:" + methodName + ",描述:" + monitor.value());
        System.out.println("[Monitor] 参数:" + java.util.Arrays.toString(args));

        Object result = null;
        try {
            result = pjp.proceed();  // 继续执行目标方法
            System.out.println("[Monitor] 方法正常返回,结果:" + result);
        } catch (Throwable ex) {
            System.out.println("[Monitor] 方法抛出异常:" + ex.getMessage());
            throw ex; // 记得重新抛出
        } finally {
            long end = System.currentTimeMillis();
            System.out.println("[Monitor] 方法耗时:" + (end - start) + " ms");
        }

        // 可以在这里,改变返回值
        return result;
    }
}
4. 启动测试
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Autowired
private OrderService orderService;

@Test
public void testCreateOrder() {
    String result = orderService.createOrder("P001");
    System.out.println("最终返回结果:" + result);
}

@Test
public void testCancelOrder() {
    String result = orderService.cancelOrder("O123");
    System.out.println("最终返回结果:" + result);
}
6. 测试输出
1
2
3
4
5
6
[Monitor] 开始调用方法:OrderService.createOrder(..),描述:下单接口
[Monitor] 参数:[P001]
执行 createOrder,商品ID:P001
[Monitor] 方法正常返回,结果:订单号: 1714333444110
[Monitor] 方法耗时:12 ms
最终返回结果:订单号: 1714333444110

7. Spring AOP 实现原理

  • 动态代理
    Spring AOP 是基于动态代理实现的,有两种方式:

    • JDK 动态代理:如果目标对象实现了接口,就用 JDK Proxy。
    • CGLIB 动态代理:如果目标对象没有接口,就用 CGLIB 生成子类代理。
  • Advisor 与 Advice

    • Advice:通知,比如 BeforeAdvice、AfterAdvice 等。
    • Advisor:等于 Pointcut(切点) + Advice(通知)
      ⇒ Advisor 其实是切点+通知的绑定器
  • BeanPostProcessor —— 代理生成关键

    • 核心类:AnnotationAwareAspectJAutoProxyCreator
    • 这个 BeanPostProcessor 在 Spring 容器创建 Bean 后,会检查:
      • 是否需要为这个 Bean 创建代理。
      • 如果需要,就动态生成代理对象,替换原来的 Bean!
  • 织入流程(Weaving)

    • 解析切面类(@Aspect),找出 Pointcut 和 Advice。
    • 为每个普通 Bean 检查是否匹配 Advisor。
    • 如果匹配:
      • 创建代理(Proxy)。
      • 把原 Bean 换成代理 Bean。
  • 整体流程图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Bean初始化流程
AnnotationAwareAspectJAutoProxyCreator
扫描所有切面(Advisor)
给当前Bean匹配合适的Advisor
需要增强?
是 → 创建代理对象(JDK / CGLIB)
否 → 保持原样

详细分步骤版

步骤 内容
1 Spring 启动时,@Aspect 注解的切面类会被解析,注册成 Advisor。
2 每次有 Bean 初始化时,AnnotationAwareAspectJAutoProxyCreator 介入。
3 给这个 Bean 找一圈,哪些 Advisor 能作用在它身上。
4 如果找到了,就用 JDK Proxy / CGLIB 生成代理对象。
5 把代理对象注册进容器,代替原来的 Bean。
6 调用代理对象时,会走 Advice 逻辑,比如前置、后置、环绕通知。

Spring AOP = 容器后处理 + 自动织入 + 动态代理

示例:开启注解驱动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 声明这是一个配置类(相当于 XML 配置文件)
@Configuration 
// 自动向容器中注入了 AnnotationAwareAspectJAutoProxyCreator 这个超核心的 BeanPostProcessor!
// 这个 Processor 能:扫描所有的切面(@Aspect),动态为匹配的 Bean 生成代理对象,实现横切逻辑(Before、After、Around等)
// proxyTargetClass = false(默认),false:优先用 JDK 动态代理(接口代理),如果有接口就用接口;true:强制用 CGLIB 子类代理(即使有接口也不用 JDK Proxy)。
// exposeProxy是否把代理对象暴露到当前线程上下文?true:可以通过 AopContext.currentProxy() 获取到当前的代理对象;false:默认不暴露。
@EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = true)
// 扫描 com.example 包,自动注册组件(比如你的 Service、Controller、Aspect类)
@ComponentScan("com.example") 
public class AopConfig {}

8.使用与测试

示例:

1. 依赖准备
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- Spring AOP -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
</dependency>

<!-- AspectJ Runtime (织入支持) -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

<!-- Spring Test 测试支持 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
</dependency>

<!-- JUnit5 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
</dependency>
2. 配置类(AopConfig)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false) // EnableAspectJAutoProxy 开启 AOP 自动代理,自动寻找bean中带有aspect注解的类,为期创建代理类,proxyTargetClass = false 优选JDK代理
@ComponentScan("com.example") // 扫描 Service 和 Aspect
public class AopConfig {
    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}
3. 业务类(Service)
1
2
3
4
5
6
@Service
public class MyService {
    public void doWork(String taskName) {
        System.out.println("Doing work: " + taskName);
    }
}
4. 切面类(Aspect)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.MyService.doWork(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("[AROUND BEFORE] - " + joinPoint.getSignature().getName());
        Object result = joinPoint.proceed(); // 调用目标方法
        System.out.println("[AROUND AFTER] - " + joinPoint.getSignature().getName());
        return result;
    }

    @Before("execution(* com.example.MyService.doWork(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("[BEFORE] - " + joinPoint.getSignature().getName());
    }

    @AfterReturning("execution(* com.example.MyService.doWork(..))")
    public void afterReturningAdvice(JoinPoint joinPoint) {
        System.out.println("[AFTER RETURNING] - " + joinPoint.getSignature().getName());
    }
}
5. 测试启动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 让 JUnit5 支持 Spring
@ExtendWith(SpringExtension.class)
// 告诉测试用哪个 Spring 配置
@ContextConfiguration(classes = {AopConfig.class, MyService.class, LoggingAspect.class})
public class AopTest {

    @Autowired
    private MyService service;

    @Test
    void testAspect() {
        service.doWork("test");
        // 观察控制台:[AROUND BEFORE] ... [BEFORE] ... Doing work: test ... [AFTER RETURNING] ... [AROUND AFTER]
    }
}
6. 测试结果
1
2
3
4
5
[AROUND BEFORE] - doWork
[BEFORE] - doWork
Doing work: test
[AFTER RETURNING] - doWork
[AROUND AFTER] - doWork

示例:

1. 目录结构
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
src/main/java/
 └── com/example/
      ├── config/
      │    └── MyConfig.java
      ├── aspect/
      │    └── LoggingAspect.java
      ├── service/
      │    └── MyService.java
src/test/java/
 └── com/example/
      └── AopTest.java
2. 配置类:MyConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.config;

import com.example.aspect.LoggingAspect;
import com.example.service.MyService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}
3. 业务类:MyService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class MyService {

    public void doWork(String task) {
        System.out.println("Doing work on: " + task);
    }

    public void doOtherWork() {
        System.out.println("Doing other work");
    }

    public String riskyOperation(String input) {
        if (input == null) {
            throw new IllegalArgumentException("Input must not be null");
        }
        return "Processed: " + input;
    }
}
4. 切面类:LoggingAspect.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.example.aspect;

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

@Aspect
@Component
public class LoggingAspect {

    // 只拦截 doWork 方法
    @Pointcut("execution(* com.example.service.MyService.doWork(..))")
    public void workPointcut() {}

    // 只拦截 riskyOperation 方法
    @Pointcut("execution(* com.example.service.MyService.riskyOperation(..))")
    public void riskyPointcut() {}

    // 通用 Before
    @Before("workPointcut()")
    public void beforeWork(JoinPoint joinPoint) {
        System.out.println("[Before] " + joinPoint.getSignature().getName() + ", args: " + joinPoint.getArgs()[0]);
    }

    // Around 带参数打印
    @Around("workPointcut()")
    public Object aroundWork(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("[Around Before] " + pjp.getSignature().getName());
        Object result = pjp.proceed();
        System.out.println("[Around After] " + pjp.getSignature().getName());
        return result;
    }

    // AfterReturning
    @AfterReturning(value = "workPointcut()", returning = "result")
    public void afterReturningWork(JoinPoint joinPoint, Object result) {
        System.out.println("[AfterReturning] " + joinPoint.getSignature().getName());
    }

    // AfterThrowing 专门处理 riskyOperation 抛异常的情况
    @AfterThrowing(value = "riskyPointcut()", throwing = "ex")
    public void afterThrowingRisky(JoinPoint joinPoint, Exception ex) {
        System.out.println("[AfterThrowing] " + joinPoint.getSignature().getName() + ", Exception: " + ex.getMessage());
    }

    // 通用 After
    @After("workPointcut() || riskyPointcut()")
    public void afterAny(JoinPoint joinPoint) {
        System.out.println("[After] " + joinPoint.getSignature().getName());
    }
}
5. 测试类:AopTest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.example;

import com.example.config.MyConfig;
import com.example.service.MyService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

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

    @Autowired
    private MyService myService;

    // Around Before ➔ Before ➔ 业务方法 ➔ AfterReturning ➔ After ➔ Around After
    @Test
    void testDoWork() {
        myService.doWork("TestTask");
    }

    @Test
    void testDoOtherWork() {
        myService.doOtherWork(); // 这个不会被AOP拦截
    }

    // 正常流程:业务方法 ➔ After
    @Test
    void testRiskyOperation_success() {
        String result = myService.riskyOperation("safe input");
        System.out.println("Result: " + result);
    }

    // 抛异常:AfterThrowing 捕获异常 ➔ After
    @Test
    void testRiskyOperation_fail() {
        try {
            myService.riskyOperation(null);
        } catch (Exception ignored) {
        }
    }
}

总结

Spring 框架是 Java 生态的基石,必知必会。