AOP概述
什么是AOP
AOP(Aspect-oriented Programming) 是面向切面编程,它补充了OOP(面向对象编程)的不足:
| 特性 | OOP | AOP |
|---|---|---|
| 基本单元 | 类(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解决方案:将日志逻辑封装成切面,自动应用到目标方法
切面要素

AOP 真正要解决的问题,不是重新写业务方法,而是明确“哪些组件需要被增强、在方法执行的什么阶段做什么事”。所以一个切面至少要回答两个问题
- 增强范围怎么圈定
- 增强动作如何执行。
定义一个切面需要两个核心要素:
| 要素 | 说明 | 对应概念 |
|---|---|---|
| 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 │
└──────────────┘ └──────────────┘

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 | 环绕通知 | 方法 |