作为一名开发者,你是否曾为每个业务方法都要手动添加权限校验、日志打印、性能监控而感到烦恼?在一个订单系统中,新增、修改、删除、查询等每个方法都需要记录操作日志,如果每个方法都手动写一遍 log.info(),不仅代码重复臃肿,更让维护成本急剧上升。立即体验AI助手,它能够快速生成高质量的代码解决方案,帮助开发者更好地理解类似 AOP 这样的核心编程思想如何从根本上解决这类问题。今天,我们就来深入讲解 Spring 框架中的核心功能——AOP(Aspect Oriented Programming,面向切面编程),系统梳理其核心概念、实现原理以及面试中的高频考点。
一、痛点切入:为什么需要AOP?

先看一段典型的业务代码:
@PostMapping("/delete")public BaseResponse deleteApp(long id) { User user = checkPermission(); // 重复代码1 if (!user.isAdmin()) { // 重复代码2 throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } log.info("开始删除应用,id=" + id); // 重复代码3 long start = System.currentTimeMillis(); // 真正的业务逻辑... appService.delete(id); long end = System.currentTimeMillis(); log.info("删除耗时:" + (end - start) + "ms"); return BaseResponse.success(); } @PostMapping("/update") public BaseResponse updateApp(App app) { User user = checkPermission(); // 同样重复 if (!user.isAdmin()) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } log.info("开始更新应用"); // 真正的业务逻辑... }
这段代码存在三大痛点:
代码重复严重:权限校验、日志记录、性能监控在每个方法中反复出现
耦合度过高:业务逻辑与横切关注点(权限、日志)混杂在一起
维护成本高:若要修改权限校验规则,需要改动所有相关方法
AOP(面向切面编程)正是为解决这些问题而生。它将日志、安全、事务等横切关注点从业务逻辑中剥离,显著提升代码的模块化程度-。
二、核心概念详解
概念 A:AOP 标准定义
AOP(Aspect Oriented Programming,面向切面编程) 是一种编程范式,通过将横切关注点与核心业务逻辑解耦,在不修改原有业务代码的前提下,对方法进行增强,统一处理日志、事务、权限、监控等横切逻辑-1。
生活化类比:想象一个小区的安保系统-2
小区 = 整个应用程序
每家每户 = 各个业务类(Controller、Service)
大门 = 方法入口
保安 = 切面(Aspect)
访客规则 = 切入点表达式(Pointcut)
检查流程 = 通知(Advice)
保安(切面)的工作是:按照规则(切入点)对进入大门(连接点)的人进行检查(通知),而住户(目标对象)完全不需要关心安保逻辑。
概念 B:七大核心术语
| 概念 | 英文 | 说明 | 对应代码示例 |
|---|---|---|---|
| 切面 | Aspect | 横切关注点的模块化 | @Aspect 标注的类 |
| 连接点 | Join Point | 可以被拦截的方法执行点 | Controller/Service 中所有方法 |
| 切入点 | Pointcut | 真正需要被拦截的连接点(筛选条件) | @annotation(authCheck) |
| 通知/增强 | Advice | 在切入点执行的逻辑 | doInterceptor() 方法 |
| 目标对象 | Target | 被代理的对象 | 标注了 @AuthCheck 的类 |
| 织入 | Weaving | 将切面应用到目标对象的过程 | Spring 运行时自动完成 |
| 引入 | Introduction | 动态添加方法或字段(不常用) | — |
-2
三、概念关系与区别总结
用一句话理清它们的逻辑关系:
切面(Aspect) 通过 切入点(Pointcut) 筛选 连接点(Join Point) ,在 织入(Weaving) 时将 通知(Advice) 应用到 目标对象(Target) 上。
核心区分要点:
连接点 vs 切入点:连接点是所有可拦截的候选方法,切入点是筛选后真正需要增强的那些方法-2
切面 vs 通知:切面是模块的整体,通知是具体在何时执行的增强动作-1
四、代码示例实战
4.1 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
4.2 方式一:通过 execution 表达式配置
@Component @Aspect public class PerformanceAspect { @Around("execution( com.example.service.impl..(..))") public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { long begin = System.currentTimeMillis(); // ⭐ 调用原始业务方法 Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); log.info("【{}】执行耗时: {}ms", joinPoint.getSignature().getName(), (end - begin)); return result; } }
-1
4.3 方式二:通过自定义注解配置
首先定义权限校验注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthCheck { String mustRole() default "admin"; }
然后编写切面类:
@Component @Aspect public class AuthInterceptor { @Around("@annotation(authCheck)") public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable { // 前置增强:权限校验 User currentUser = UserContext.getCurrentUser(); if (currentUser == null || !currentUser.getRole().equals(authCheck.mustRole())) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 执行目标方法 return joinPoint.proceed(); } }
4.4 对比展示
❌ 没有AOP时:每个业务方法都包含重复的权限校验代码
✅ 使用AOP后:业务代码只关心核心逻辑
@PostMapping("/delete") @AuthCheck(mustRole = "admin") // 仅一行注解 public BaseResponse deleteApp(long id) { // 只写真正的业务逻辑... appService.delete(id); return BaseResponse.success(); }
-2
4.5 五种通知类型
| 通知类型 | 执行时机 | 使用场景 |
|---|---|---|
@Before | 目标方法执行之前 | 权限预检、参数校验 |
@After | 目标方法执行之后(无论是否异常) | 资源释放、清理操作 |
@AfterReturning | 目标方法正常返回后 | 日志记录、返回值处理 |
@AfterThrowing | 目标方法抛出异常时 | 异常监控、告警通知 |
@Around | 环绕目标方法(功能最强) | 性能监控、事务管理、参数修改 |
-1
关键说明:@Around 是最强大的通知类型,能够精确控制方法执行的全过程,必须调用 joinPoint.proceed() 来执行原始业务方法,且返回值需指定为 Object 类型-1。若需要获取返回值或捕获异常,务必使用 @Around,因为 @Before 无法获取返回结果,@AfterReturning 无法捕获异常信息-8。
五、底层原理:动态代理
5.1 核心原理
Spring AOP 的底层本质是动态代理:用动态代理包装原始 Bean,让方法执行过程被增强-27。最终生成一个代理对象,调用方法时先走代理逻辑,再走原方法,从而实现增强-。
5.2 JDK 动态代理 vs CGLIB
Spring AOP 根据目标类是否实现接口,自动选择不同的动态代理技术:
| 对比项 | JDK 动态代理 | CGLIB |
|---|---|---|
| 代理方式 | 接口代理 | 子类代理 |
| 是否依赖接口 | 必须有接口 | 不需要接口 |
| 调用性能 | 调用成本低 | 生成类成本高,调用快 |
| 能否代理 final 方法 | ❌ 不可 | ❌ 也不可 |
| 依赖 | Java 原生,无需额外依赖 | 需引入 CGLIB 库 |
| 代理类命名 | $Proxy0 格式 | Service$$EnhancerBySpringCGLIB |
-27
-26
5.3 Spring 的默认策略
Spring Framework 默认:优先使用 JDK 动态代理,当目标类没有实现接口时自动切换为 CGLIB-
Spring Boot 2.x+ 默认:将默认值改为了 CGLIB-
可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用 CGLIB-28
5.4 底层依赖的关键技术
Spring AOP 底层依赖两大核心技术:
反射(Reflection) :JDK 动态代理的核心,运行时获取方法元信息并调用
字节码生成(Bytecode Generation) :CGLIB 基于 ASM 字节码技术,动态生成目标类的子类-26
Spring 通过 BeanPostProcessor 机制,在 Bean 初始化阶段创建代理对象,而非在容器启动时立即创建-27。
六、高频面试题与参考答案
面试题1:什么是 Spring AOP?它的核心概念有哪些?
标准答案:
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,通过将横切关注点(如日志、事务、权限)从业务逻辑中剥离,实现代码解耦。Spring AOP 的核心概念包括:
切面(Aspect) :横切关注点的模块化
连接点(Join Point) :可被拦截的方法执行点
切入点(Pointcut) :筛选连接点的匹配规则
通知(Advice) :在切入点执行的增强逻辑,包含 @Before、@After、@AfterReturning、@AfterThrowing、@Around 五种类型
目标对象(Target) :被增强的业务对象
织入(Weaving) :将切面应用到目标对象的过程
-2
-1
面试题2:Spring AOP 的实现原理是什么?JDK 动态代理和 CGLIB 有什么区别?
标准答案:
Spring AOP 的核心原理是动态代理,运行时动态生成代理对象,在目标方法前后织入增强逻辑-20。
JDK 动态代理与 CGLIB 的核心区别:
| 区别点 | JDK 动态代理 | CGLIB |
|---|---|---|
| 代理方式 | 基于接口 | 基于子类继承 |
| 依赖条件 | 目标类必须有接口 | 无需接口,但不能是 final 类 |
| 性能 | 反射调用,调用开销小 | 字节码生成,调用速度快 |
| 依赖 | Java 原生,无额外依赖 | 需引入 CGLIB 库 |
Spring 默认优先使用 JDK 动态代理,目标类无接口时自动切换为 CGLIB。Spring Boot 2.x 起将默认改为 CGLIB。
-27
-26
面试题3:@Around 通知和其他通知类型的区别是什么?为什么推荐用 @Around 处理操作日志?
标准答案:
@Around 是功能最强的通知类型,能够精确控制方法执行的全过程,其他四种通知只能在特定时机执行。
推荐用 @Around 处理操作日志,原因如下:
@Before拿不到方法返回结果@AfterReturning捕获不到异常信息@AfterThrowing拿不到正常返回值只有
@Around能通过proceed()方法统一获取返回值、捕获异常、精确计算耗时
-8
-1
面试题4:Spring AOP 和 AspectJ 有什么区别?
标准答案:
Spring AOP 和 AspectJ 都是 Java 中实现 AOP 的框架,但定位完全不同-37:
Spring AOP:Spring 框架自带的轻量级 AOP 实现,只支持运行时代理,底层使用 JDK 动态代理或 CGLIB,只能拦截 Spring 容器管理的 Bean 方法。优点是简单、零配置成本。
AspectJ:功能完整的 AOP 框架,支持编译时、类加载时、运行时三种织入方式,能拦截构造函数、静态方法、字段访问等更多类型的连接点。功能更强大但配置更复杂。
七、结尾总结
核心知识点回顾
AOP 的价值:将横切关注点从业务逻辑中剥离,消除重复代码,降低耦合度
七大核心概念:切面、连接点、切入点、通知、目标对象、织入、引入
五种通知类型:@Before、@After、@AfterReturning、@AfterThrowing、@Around
底层原理:动态代理(JDK 代理 + CGLIB),依赖反射和字节码生成技术
面试关键点:能说清动态代理的选择策略及两种代理方式的区别
易错点提醒
⚠️ 常见误区:
切面类必须被 Spring 容器管理(添加
@Component),否则 AOP 不会生效-28@Around通知必须调用proceed()且只能调用一次CGLIB 无法代理
final类或final/static/private方法-28自定义注解必须声明
@Retention(RetentionPolicy.RUNTIME),否则运行期无法读取-8
下一篇我们将深入讲解 Spring 事务管理的实现原理,敬请关注!
