如果你是 AI夸夸助手 的忠实读者,欢迎来到本期技术深度拆解。
面向切面编程(Aspect-Oriented Programming,AOP)是 Spring 框架两大核心特性之一,与 IoC 并称为 Spring 的基石-10。据统计,2025 年 Java 生态中已有

很多开发者虽然能用 AOP,却在面试中被问“JDK 动态代理和 CGLIB 有什么区别”时卡住,或者在自己的项目中遇到切面不生效却不知从何排查。今天这篇文章将带你从零系统掌握 Spring AOP:先搞懂核心概念,再看底层原理,最后附上可直接运行的代码和面试高频题,让你真正“理解概念、理清逻辑、看懂示例、记住考点”。
一、痛点切入:为什么你的代码越来越“脏”?

在传统的面向对象编程(OOP)中,代码按照业务功能垂直组织。这种模式在处理“横切”需求时会暴露出明显的痛点。来看一段典型代码:
@Service public class UserService { public void createUser(String username) { // 手动日志 - 重复代码 System.out.println("【日志】开始执行 createUser,参数:" + username); // 手动性能监控 - 重复代码 long start = System.currentTimeMillis(); // 核心业务逻辑 System.out.println("创建用户:" + username); // 手动性能监控结束 - 重复代码 long end = System.currentTimeMillis(); System.out.println("【性能】createUser 耗时:" + (end - start) + "ms"); // 手动日志结束 - 重复代码 System.out.println("【日志】createUser 执行完成"); } public void deleteUser(Long userId) { // 同样的日志和性能代码又写了一遍... // 如果有 50 个方法,就要写 50 遍! } }
这段代码的三大问题:
| 问题 | 说明 |
|---|---|
| 代码冗余 | 日志、性能监控、事务管理等代码在每个方法中重复出现,开发效率低 |
| 关注点混杂 | 核心业务逻辑与系统级服务交织在一起,代码难以阅读和维护 |
| 扩展性差 | 若要修改日志格式或增加新的切面功能(如安全校验),需修改所有相关方法 |
这正是 AOP 要解决的问题。AOP 将这些分散在多处、与核心业务无关的横切关注点(Cross-cutting Concerns)抽取出来,封装成独立的“切面”,在不修改原有业务代码的前提下动态织入增强逻辑-。
二、核心概念:AOP 的七个关键术语
在正式编码之前,必须掌握 AOP 的七个核心概念-22:
| 术语 | 英文 | 含义 | 类比 |
|---|---|---|---|
| 连接点 | Join Point | 程序执行过程中可以插入切面的点(如方法调用、异常抛出) | 地铁站里的每一个出入口 |
| 切点 | Pointcut | 匹配连接点的表达式,决定在哪些连接点上应用通知 | 指定从哪个出口出站 |
| 通知 | Advice | 在特定连接点上执行的动作(前置/后置/环绕等) | 在出口处做的事情(刷卡、安检) |
| 切面 | Aspect | 切点 + 通知的封装模块 | 整个安检流程 |
| 目标对象 | Target Object | 被增强的原始业务对象 | 乘客本人 |
| 代理 | Proxy | AOP 框架生成的对象,包裹目标对象以插入切面逻辑 | 安检通道(乘客经过) |
| 织入 | Weaving | 将切面代码与目标对象关联的过程 | 把安检规则施加到乘客身上 |
一句话总结:在 连接点 中,用 切点 选定一批目标方法,在这些方法执行前后,通过 通知 插入增强逻辑;将切点和通知打包就是 切面;通过 织入 生成 代理对象,最终增强 目标对象。
通知(Advice)的五种类型-11:
| 注解 | 执行时机 | 典型用途 |
|---|---|---|
@Before | 目标方法执行前 | 参数校验、权限检查 |
@After | 目标方法执行后(无论是否异常) | 资源清理、释放连接 |
@AfterReturning | 目标方法正常返回后 | 记录返回值、缓存更新 |
@AfterThrowing | 目标方法抛出异常后 | 异常告警、事务回滚 |
@Around | 环绕整个方法执行,可控 | 性能监控、事务管理(最强大) |
@Around 通知是使用频率最高的类型,因为它既能控制目标方法是否执行,又能同时获取方法执行前后的完整信息。注意必须调用 proceed() 才能执行目标方法,并且要有返回值。
三、代理模式与动态代理:AOP 的底层基石
Spring AOP 的实现本质上依赖于 代理模式(Proxy Pattern) ,这是理解 AOP 原理的关键-21。
3.1 静态代理
先理解什么是代理:代理类作为中间层,在调用真实对象前后插入额外逻辑。用一个房屋中介的例子说明-21:
// 1. 抽象主题:定义业务接口 public interface HouseSubject { void rentHouse(); // 房屋租赁 } // 2. 真实主题:业主(真正的业务逻辑) public class RealHouseSubject implements HouseSubject { @Override public void rentHouse() { System.out.println("业主执行:签订租约 → 交付房屋"); } } // 3. 代理类:中介(在真实业务前后插入增强逻辑) public class HouseProxy implements HouseSubject { private HouseSubject realSubject; public HouseProxy(HouseSubject realSubject) { this.realSubject = realSubject; } @Override public void rentHouse() { // 前置增强 System.out.println("【中介】审核租客资质 → 准备租赁合同"); // 调用真实业务 realSubject.rentHouse(); // 后置增强 System.out.println("【中介】收取中介费 → 跟进租后服务"); } } // 使用 public class Client { public static void main(String[] args) { HouseSubject proxy = new HouseProxy(new RealHouseSubject()); proxy.rentHouse(); // 调用代理,自动获得增强功能 } }
静态代理的缺点也很明显:每个真实类都需要写一个对应的代理类,当项目中有上百个类需要增强时,代码量会急剧膨胀。
3.2 动态代理:Spring AOP 的灵魂
动态代理在运行时动态生成代理类,无需为每个类手动编写代理。Spring AOP 支持两种动态代理方式--11:
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 基于接口生成代理类 | 基于继承生成目标类的子类 |
| 适用条件 | 目标类必须实现至少一个接口 | 目标类无接口(或强制使用) |
| 无法代理的对象 | 无接口的类 | final 类 / final 方法 |
| 性能 | 反射调用,略低 | 直接调用,略高 |
| 依赖 | JDK 原生,无需额外依赖 | 需要 CGLIB 库(Spring 已整合) |
Spring AOP 默认优先使用 JDK 动态代理;当目标类未实现任何接口时,自动切换为 CGLIB 代理-。可通过配置强制使用 CGLIB:@EnableAspectJAutoProxy(proxyTargetClass = true)。
四、代码示例:从零搭建 Spring Boot AOP
4.1 添加依赖
在 pom.xml 中添加 AOP 起步依赖-29:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
4.2 创建切面类
下面是一个完整的日志切面,实现了统一日志记录 + 接口耗时统计-12:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.; import org.springframework.stereotype.Component; @Aspect // 标记为切面类 @Component // 交由 Spring 容器管理 public class LoggingAspect { / 切点:匹配 com.example.service 包下所有类的所有方法 execution 表达式格式:[修饰符] 返回类型 包名.类名.方法名(参数) / @Pointcut("execution( com.example.service..(..))") public void servicePointcut() {} // 前置通知:方法执行前执行 @Before("servicePointcut()") public void logBefore(JoinPoint joinPoint) { System.out.println("【前置】调用前:" + joinPoint.getSignature().getName()); } // 后置通知:方法执行后执行(无论是否异常) @After("servicePointcut()") public void logAfter(JoinPoint joinPoint) { System.out.println("【后置】方法完成:" + joinPoint.getSignature().getName()); } // 环绕通知:最强大,可控制方法执行流程并统计耗时 @Around("servicePointcut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); long startTime = System.currentTimeMillis(); System.out.println("【Around 前】进入方法:" + methodName); // 执行目标方法 Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); System.out.println("【Around 后】方法 " + methodName + " 耗时:" + (endTime - startTime) + "ms"); return result; } }
关键点解析:
@Aspect+@Component:声明切面类并由 Spring 管理-47@Pointcut:定义切点表达式,复用性强ProceedingJoinPoint:环绕通知专用,调用proceed()执行目标方法joinPoint.getSignature():获取被拦截的方法信息
4.3 自定义注解实现 AOP(更优雅的方式)
除了用表达式匹配,还可以通过自定义注解实现更精准的拦截-29:
// 1. 创建自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable { } // 2. 在切面中使用注解作为切入点 @Pointcut("@annotation(com.example.demo.Loggable)") public void loggablePointcut() {} // 3. 在目标方法上添加注解 @Loggable public User getUserById(Long id) { return userRepository.findById(id); }
4.4 新旧方式对比
| 维度 | 传统方式(OOP) | AOP 方式 |
|---|---|---|
| 代码位置 | 日志代码分散在各业务方法中 | 日志逻辑集中在切面类 |
| 修改成本 | 改一处需改所有方法 | 只改切面类 |
| 业务代码纯净度 | 日志代码与业务逻辑混在一起 | 业务代码只关注核心逻辑 |
| 新增功能 | 需要修改所有相关方法 | 只需添加新的切面类 |
五、底层原理:代理对象是如何生成的?
当 Spring 容器初始化 Bean 时,AOP 的处理流程如下--20:
Bean 后置处理:
AnnotationAwareAspectJAutoProxyCreator在 Bean 初始化后介入代理判断:检查 Bean 是否匹配任意切点
代理创建:通过
DefaultAopProxyFactory决定代理方式目标类实现了接口 → JDK 动态代理
目标类无接口 → CGLIB 代理
通知链构建:将匹配的通知构建为
MethodInterceptor链代理返回:返回代理对象,注入到其他 Bean 中
底层依赖的知识点:动态代理的核心依赖于 Java 反射机制——JDK 代理通过 InvocationHandler.invoke() 实现,CGLIB 通过 ASM 字节码技术生成子类。这也是面试中经常被追问“反射的应用场景”的原因-。
Spring AOP 属于 运行时织入(Runtime Weaving) ,即在程序运行时动态生成代理,而非在编译时修改字节码。这与 AspectJ 形成对比-22:
| 对比项 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时动态代理 | 编译时/类加载时织入 |
| 连接点范围 | 仅支持方法级别 | 支持字段、构造器、静态代码块等 |
| 性能 | 略有损耗(运行时生成代理) | 更高(编译时已织入) |
| 使用复杂度 | 简单轻量,适合大多数场景 | 复杂但功能强大 |
六、高频面试题与参考答案
题目1:什么是 AOP?它的核心思想是什么?
标准答案:AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,其核心思想是将横切关注点(如日志、事务、安全等)从核心业务逻辑中分离出来,封装成可重用的切面模块,通过预编译方式或运行期动态代理在目标方法前后织入增强逻辑,从而提高代码的模块化程度和可维护性-。
踩分点:① 说出全称 ② 点出“横切关注点” ③ 说明“分离”和“织入”
题目2:Spring AOP 的底层原理是什么?JDK 动态代理和 CGLIB 有什么区别?
标准答案:Spring AOP 底层基于动态代理实现,支持两种代理方式-1:
JDK 动态代理:要求目标类实现接口,通过
java.lang.reflect.Proxy和InvocationHandler在运行时生成代理类,基于反射调用目标方法CGLIB 代理:针对无接口的类,通过字节码技术生成目标类的子类,重写父类方法实现增强,但无法代理
final类或final方法
Spring 默认优先使用 JDK 动态代理,当目标类未实现接口时自动切换为 CGLIB。
踩分点:① 说出两种代理方式 ② 说清各自原理 ③ 指出适用条件差异 ④ 说明 Spring 的默认选择策略
题目3:AOP 中的通知有哪些类型?各有什么特点?
标准答案:AOP 有五种通知类型-22:
@Before:前置通知,在目标方法执行前触发,适用于参数校验、权限控制@After:后置通知,方法执行后触发(无论是否异常),适用于资源清理@AfterReturning:返回通知,方法正常返回后触发,可获取返回值@AfterThrowing:异常通知,方法抛出异常后触发,适用于异常告警@Around:环绕通知,最强大,可在方法执行前后都插入逻辑,需手动调用proceed()执行目标方法
踩分点:① 列出五种类型 ② 说清各自触发时机 ③ 说明 @Around 的特殊性(需手动调用 proceed())
题目4:Spring AOP 切面不生效可能有哪些原因?
标准答案:常见原因如下-46:
缺少 AOP 依赖或未启用
@EnableAspectJAutoProxy切面类未被
@Component注解且未被 Spring 容器扫描到切点表达式写错,导致未匹配到任何目标方法
目标方法为
private或final(CGLIB 无法代理)同类内部方法调用
this.method(),绕过了代理对象目标对象非 Spring 容器管理的 Bean
踩分点:① 依赖/配置问题 ② 切点表达式问题 ③ 同类调用问题 ④ final/private 限制
题目5:execution 切入点表达式怎么写?
标准答案:execution 表达式格式为-22:
execution([修饰符] 返回类型 包名.类名.方法名(参数))常用示例:
execution( com.example.service..(..)):匹配该包下所有类的所有方法execution(public void com.example...(..)):匹配com.example包及子包下所有public void方法execution( com.example.UserService.get(..)):匹配UserService中以get开头的方法
七、结尾总结
本文系统讲解了 Spring AOP 的核心知识点:
核心知识回顾:
是什么:AOP 是一种解决横切关注点的编程范式,将日志、事务等公共功能从业务逻辑中剥离
怎么用:通过
@Aspect+@Component定义切面,配合五种通知类型在目标方法前后插入逻辑为什么能工作:底层依赖动态代理——JDK 动态代理(基于接口)和 CGLIB 代理(基于继承)
面试考什么:概念理解、代理区别、通知类型、失效原因、表达式写法
重点与易错点:
切面不生效最常见的原因:同类内部调用
this.method()绕过代理切点表达式写错导致切面不生效,务必用
@Pointcut单独定义便于调试@Around通知必须调用proceed()并返回结果,否则目标方法不会执行
进阶预告:下一篇文章将深入讲解 AOP 在实际项目中的最佳实践——如何用 AOP 实现可插拔的日志追踪系统、分布式链路追踪以及性能监控,敬请关注!
📚 参考资料
[1] AOP(面向切面编程)——开发者社区. 阿里云开发者社区,2025.
[2] Spring AOP 深度解析与项目实战. 腾讯云开发者社区,2025.
[3] 搞懂 AOP:核心概念 + 实战案例 + 事务失效终极排查指南. 华为云开发者社区,2025.
[4] Spring AOP实现原理. 华为云开发者社区,2025.
[5] 如何在Spring Boot项目中使用Spring AOP. 腾讯云开发者社区,2025.
[6] 百度后端开发(Java)面试题精选. Java知识分享网,2026.
[7] 谈谈 Spring 的 AOP. quanxiaoha.com,2026.
[8] 如何在Spring中实现基于注解的AOP. 图灵资讯,2026.
[9] Java Web学习日报——AOP面向切面编程实战. cnblogs.com,2026.
扫一扫微信交流