时间:北京时间2026年4月10日
Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架两大核心特性之一,与IoC(Inversion of Control,控制反转)并称Spring的“左膀右臂”-14。很多开发者的痛点恰恰在于:会用AOP注解,却说不出底层原理;知道动态代理,却分不清JDK和CGLIB的区别;面试被问到AOP实现机制时,只能支支吾吾地回答“Spring会自动生成代理”。

本文由ai小胖智能助手进行资料整理与学习路线规划,将从痛点切入,带你一步步理解Spring AOP的核心概念、实现原理、代码实战和高频面试考点,建立完整知识链路。
一、痛点切入:为什么需要AOP?

先看一个典型的开发场景——权限校验。在没有AOP的情况下,代码往往长这样:
@PostMapping("/delete") public BaseResponse deleteApp(long id) { User user = checkPermission(); // 重复代码 if (!user.isAdmin()) { // 重复代码 throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 真正的业务逻辑... } @PostMapping("/update") public BaseResponse updateApp(App app) { User user = checkPermission(); // 重复代码 if (!user.isAdmin()) { // 重复代码 throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 真正的业务逻辑... }
这段代码存在明显的代码冗余问题:日志记录、权限校验、事务管理、性能监控等横切逻辑(Cross-Cutting Concerns)被迫散落在各个业务方法中,导致:
耦合度高:业务代码与辅助功能代码混杂,难以单独维护
扩展性差:添加一个新功能需要在数十甚至上百个方法中重复修改
代码可读性差:核心业务逻辑被淹没在大量“样板代码”中
复用困难:相同的横切逻辑难以被多个模块共享
这正是AOP出现的根本原因——将横切关注点从核心业务中剥离,实现代码复用和模块解耦。
二、核心概念讲解:切面(Aspect)
标准定义
切面(Aspect) :AOP的核心模块化单元,将横切关注点(如日志、权限、事务)封装成一个独立的类,负责定义“在什么时候、什么地方、执行什么增强逻辑”。
生活化类比
小区门禁系统场景-6:
🏢 小区 = 整个应用程序
🏠 每家每户 = 各个业务类(Controller、Service)
🚪 大门入口 = 方法调用点(连接点)
👮 保安 = 切面(Aspect)
📋 访客规则 = 切入点表达式(Pointcut)
✅ 检查流程 = 通知(Advice)
保安(切面)的职责是统一处理所有访客进入小区的检查工作,而不是要求每家每户自己守在门口查访客。同理,切面的职责是统一处理所有业务方法中共同需要的横切功能,而不是让每个方法都重复写相同的代码。
代码示例:定义切面
@Aspect // 声明这是一个切面 @Component // 交给Spring管理 public class LoggingAspect { // 整个类就是一个切面 @Before("execution( com.example.service..(..))") public void logBeforeMethod(JoinPoint joinPoint) { System.out.println("方法执行前: " + joinPoint.getSignature().getName()); } }
有了切面后,业务代码回归纯粹:
@PostMapping("/delete") public BaseResponse deleteApp(long id) { // 真正的业务逻辑,无需手动写日志代码 }
三、关联概念讲解:通知(Advice)与切入点(Pointcut)
通知(Advice)
通知定义了切面在特定连接点上执行的“动作”——即“做什么”。Spring AOP提供了5种通知类型-11:
| 通知类型 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before | 目标方法执行之前执行 |
| 后置通知 | @After | 目标方法执行之后执行(无论是否抛异常) |
| 返回通知 | @AfterReturning | 目标方法正常返回后执行 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常时执行 |
| 环绕通知 | @Around | 包裹整个目标方法,可控制方法是否执行、修改返回值,功能最强 |
切入点(Pointcut)
切入点定义了切面通知需要应用到哪些连接点上——即“在哪里做”。它是一个筛选规则,通过表达式来匹配需要被增强的方法-23。
// 定义切入点:匹配 service 包下所有类的所有 public 方法 @Pointcut("execution(public com.example.service..(..))") public void servicePointCut() {} // 在通知中引用切入点 @Before("servicePointCut()") public void beforeServiceMethod(JoinPoint joinPoint) { // 增强逻辑 }
常见切入点表达式示例
execution( com.example.service..(..)):匹配指定包下所有类的所有方法@annotation(com.example.annotation.AuthCheck):匹配标注了特定注解的方法within(com.example.controller..):匹配指定包及其子包下的所有类
四、概念关系与区别总结
理解AOP的核心概念,关键在于厘清以下逻辑关系:
| 概念 | 作用 | 一句话概括 |
|---|---|---|
| 切面(Aspect) | 横切关注点的模块化封装 | “谁来管” |
| 连接点(Join Point) | 可被拦截的方法调用点 | “哪里能管” |
| 切入点(Pointcut) | 筛选连接点的规则 | “哪些要管” |
| 通知(Advice) | 具体要执行的增强逻辑 | “管什么、何时管” |
| 目标对象(Target) | 被代理的原始对象 | “被管的对象” |
| 织入(Weaving) | 将切面应用到目标对象的过程 | “怎么管起来” |
一句话记忆:切面(Aspect)通过切入点(Pointcut)筛选连接点(Join Point),在织入(Weaving)时将通知(Advice)应用到目标对象(Target)。
五、代码示例:一个完整的日志记录切面
核心代码
@Aspect @Component public class PerformanceAspect { // 定义切入点:匹配 controller 包下的所有方法 @Pointcut("execution( com.example.controller..(..))") public void controllerMethod() {} // 环绕通知:统计方法执行时间 @Around("controllerMethod()") public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); // 执行目标方法 Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); String methodName = joinPoint.getSignature().toShortString(); System.out.println(methodName + " 执行耗时: " + (end - start) + "ms"); return result; } }
执行流程解读
当请求进入 Controller 方法时,Spring 不会直接调用原始方法
而是通过代理对象,先执行切面中定义的通知逻辑
环绕通知中调用
joinPoint.proceed()时,才真正执行原始目标方法目标方法执行完毕后,继续执行环绕通知中后续的增强逻辑
最终将结果返回给调用方-
新旧实现对比
| 对比维度 | 传统方式(无AOP) | AOP方式 |
|---|---|---|
| 代码量 | 每个方法都需要手动写统计代码 | 切面中写一次即可 |
| 维护成本 | 修改日志格式需改所有方法 | 只需修改切面 |
| 业务侵入 | 业务代码中混杂横切逻辑 | 业务代码零侵入 |
| 复用性 | 几乎无法复用 | 切面可在多模块间复用 |
六、底层原理:动态代理机制
Spring AOP的底层实现依赖于动态代理技术-。其核心本质是:用动态代理包装原始Bean,让方法执行过程被增强。
Spring AOP支持两种动态代理方式:
JDK动态代理
原理:基于接口,利用
java.lang.reflect.Proxy和InvocationHandler,在运行时生成一个实现相同接口的代理类-51适用场景:目标类实现了接口时使用
核心API:
Proxy.newProxyInstance(ClassLoader, interfaces, InvocationHandler)底层技术:反射(Reflection)
CGLIB动态代理
原理:基于继承,利用ASM字节码增强技术,在运行时生成目标类的子类作为代理类-51
适用场景:目标类没有实现接口时使用
限制:无法代理
final类或final方法底层技术:ASM字节码操作
Spring AOP的代理选择策略
Spring AOP的代理选择遵循以下规则--1:
如果目标Bean实现了接口,Spring默认使用JDK动态代理
如果目标Bean没有实现任何接口,Spring使用CGLIB
Spring Boot 2.x 开始,可以通过
spring.aop.proxy-target-class=true强制使用CGLIB
| 对比项 | JDK动态代理 | CGLIB |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承(子类) |
| 是否依赖接口 | ✅ 必须实现接口 | ❌ 不需要 |
| final方法/类 | ❌ 不可代理 | ❌ 不可代理 |
| 底层技术 | 反射 + Proxy | ASM字节码增强 |
| JDK8后性能 | 差距缩小 | 差距缩小 |
| 适用场景 | 有接口的Bean | 无接口的Bean |
七、高频面试题与参考答案
题目1:什么是Spring AOP?它解决了什么问题?
参考答案:AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,通过横向抽取机制,将分散在各个业务方法中的公共行为(如日志、权限、事务)提取到一个可重用的切面模块中-11。它解决了传统OOP中代码重复、耦合度高的痛点,让开发者能够在不修改核心业务源码的前提下,动态地添加横切功能。
踩分点:定义 + 解决的问题 + 与OOP的关系
题目2:Spring AOP的实现原理是什么?
参考答案:Spring AOP的底层基于动态代理机制,核心流程分为三步:
在Bean初始化完成后,Spring会判断该Bean是否需要应用切面增强
如果需要,Spring会根据目标类是否实现接口,选择JDK动态代理或CGLIB生成代理对象
最终将代理对象放入容器,方法调用时通过代理对象执行增强逻辑和目标方法-
踩分点:动态代理 + JDK/CGLIB + 代理时机(Bean初始化后)
题目3:JDK动态代理和CGLIB有什么区别?
参考答案:核心区别如下-51-1:
代理方式:JDK基于接口,CGLIB基于继承(生成子类)
依赖条件:JDK要求目标类必须实现接口;CGLIB不需要接口,但无法代理final类/final方法
底层技术:JDK使用反射+Proxy;CGLIB使用ASM字节码增强
默认策略:Spring根据目标类是否有接口自动选择,有接口用JDK,无接口用CGLIB
踩分点:三条以上区别 + 说明各自的适用场景
题目4:AOP通知有哪些类型?环绕通知有什么特殊之处?
参考答案:Spring AOP提供了5种通知类型:
前置通知(@Before) :目标方法执行前执行
后置通知(@After) :目标方法结束后执行(无论异常与否)
返回通知(@AfterReturning) :目标方法正常返回后执行
异常通知(@AfterThrowing) :目标方法抛出异常时执行
环绕通知(@Around) :包裹整个目标方法,可控制方法的执行和返回值,功能最强-11
环绕通知的特殊之处在于:它通过 ProceedingJoinPoint.proceed() 主动控制目标方法的执行,可以在方法前后自由安排逻辑,甚至决定是否执行目标方法或修改返回值。
踩分点:列出5种类型 + 强调环绕通知的控制能力
题目5:Spring AOP为什么对同一个类内部的self-invocation(自调用)不生效?
参考答案:Spring AOP基于代理机制实现。当通过代理对象调用方法时,切面才会生效。但类内部通过 this.methodB() 直接调用另一个方法时,this指向的是原始对象而非代理对象,因此不会触发切面逻辑。解决方案:通过 AopContext.currentProxy() 获取当前代理对象进行调用,或重新设计代码结构。
踩分点:点出代理机制 + this指向问题 + 提供解决方案
八、结尾总结
回顾全文核心知识点:
| 学习要点 | 关键内容 |
|---|---|
| AOP的核心价值 | 解耦横切关注点,实现代码复用与零侵入增强 |
| 核心概念 | 切面(Aspect)、切入点(Pointcut)、通知(Advice)、连接点(Join Point) |
| 实现原理 | JDK动态代理(基于接口)vs CGLIB(基于继承) |
| 5种通知类型 | @Before、@After、@AfterReturning、@AfterThrowing、@Around |
| 常见面试考点 | 代理机制区别、自调用失效问题、通知类型与执行顺序 |
重点关注:概念之间的关系不要混淆,代理机制的区别是面试高频考点,自调用失效问题在实际开发中非常容易踩坑,需要格外留意。
预告:下一篇文章将深入讲解 Spring AOP 与 AspectJ 的对比,以及如何自定义注解实现精细化切面控制,敬请关注!
本文由ai小胖智能助手整理输出,旨在帮助读者建立完整的技术知识链路。
扫一扫微信交流