北京时间:2026年4月10日
在Spring全家桶中,AI简单助手可以帮你快速梳理一个关键事实:AOP(面向切面编程)与IoC并称为Spring两大核心思想,是每一位Java开发者绕不开的必修课。然而不少学习者陷入“会用但不懂原理”的困境——知道在Service层加个@Transactional能开启事务,却说不出为什么;面试中被问到“JDK动态代理和CGLIB有什么区别”就卡壳。本文将从痛点出发,由浅入深讲清AOP的概念、底层原理、代码实战与面试要点,帮你建立完整知识链路。

一、痛点切入:为什么业务代码越写越臃肿?
先看一个典型场景。假设你要开发一个电商系统,包含登录、下单、支付、查询等业务方法:

public class OrderService { public void createOrder() { System.out.println("执行业务:创建订单"); } public void payOrder() { System.out.println("执行业务:支付订单"); } }
现在需求来了:每个方法都要加日志打印、性能监控、权限校验、事务控制。最常见的做法是直接在每个方法里手动添加:
public class OrderService { public void createOrder() { long start = System.currentTimeMillis(); System.out.println("【日志】开始创建订单"); // 权限校验... // 事务开启... System.out.println("执行业务:创建订单"); long end = System.currentTimeMillis(); System.out.println("【监控】耗时:" + (end - start) + "ms"); // 事务提交... } // 其他方法同样重复... }
这种写法的痛点一目了然:
代码冗余:同样的日志、监控代码在N个方法中反复出现
耦合度高:业务逻辑与横切功能纠缠在一起,改一处日志格式要改几十个文件
维护困难:新增一个横切需求,要在所有业务方法中“插桩”
可读性差:核心业务逻辑被淹没在各种非业务代码中
AOP正是为解决这些痛点而生——将日志、事务、权限等横切关注点从业务逻辑中抽离,做成一个个“切面”,在不修改原有代码的前提下自动织入到目标方法中-1-8。
二、核心概念:AOP是什么?
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它是对OOP(面向对象编程)的补充和完善。AOP通过横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序运行阶段动态应用到需要执行的地方-。
用生活类比快速理解
想象一个大型商场。每个店铺(业务方法)开业前都需要办理同样的手续:消防检查、工商登记、卫生许可。传统做法是每个店铺自己去跑一遍。AOP的做法是:商场统一设立“开业服务中心”,所有店铺只需“切入”到这个中心,手续自动办妥。这个“服务中心”就是切面,手续办理的时机就是通知。
AOP核心术语速查表
| 术语 | 英文 | 一句话解释 |
|---|---|---|
| 切面 | Aspect | 要增强的功能模块(日志、事务等),封装横切逻辑的类 |
| 连接点 | Join Point | 可以被增强的方法(程序执行中的某个点) |
| 切点 | Pointcut | 匹配规则,决定哪些连接点被真正增强 |
| 通知 | Advice | 增强逻辑的具体执行时机(前置/后置/环绕等) |
| 目标对象 | Target | 被增强的原始业务对象 |
| 织入 | Weaving | 将切面逻辑应用到目标对象并创建代理对象的过程 |
-1-8
三、关联概念:OOP与AOP的关系与区别
OOP(Object-Oriented Programming,面向对象编程)以类为模块化单元,通过封装、继承、多态实现纵向的代码复用——子类继承父类的属性和方法。
AOP以切面为模块化单元,通过横向抽取机制实现横向的代码复用——将散布在多处公共行为抽离成独立模块--。
一句话概括关系
OOP解决“是什么”的问题(纵向划分),AOP解决“做什么”的补充(横向切入);AOP不是OOP的替代品,而是OOP的延伸和补充。
对比总结
| 对比维度 | OOP | AOP |
|---|---|---|
| 模块化单元 | 类 | 切面 |
| 复用方向 | 纵向(继承) | 横向(抽取) |
| 关注点 | 核心业务逻辑 | 横切关注点 |
| 能否解决横切问题 | ❌ 不能 | ✅ 能 |
--
四、通知类型详解:五种Advice各司其职
通知决定了切面逻辑“什么时候执行”。Spring AOP支持五种通知类型:
| 通知类型 | 注解 | 执行时机 | 典型用途 |
|---|---|---|---|
| 前置通知 | @Before | 目标方法执行前 | 权限校验、参数验证 |
| 后置通知 | @After | 目标方法执行后(无论是否异常) | 释放资源、清理现场 |
| 返回通知 | @AfterReturning | 目标方法正常返回后 | 日志记录返回值 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常时 | 异常报警、回滚事务 |
| 环绕通知 | @Around | 包裹目标方法,前后均可控制 | 性能监控、事务管理 |
-8-1
💡 重点提示:@Around是最强大的通知类型,需要手动调用ProceedingJoinPoint.proceed()来执行目标方法,返回值类型必须为Object-1。其他四种通知不需要手动控制方法执行。
五、代码实战:从零搭建AOP示例
5.1 添加依赖
<!-- Spring AOP依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- AspectJ注解支持(必须) --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
-21
5.2 启用AOP自动代理
Spring Boot项目中,@SpringBootApplication已隐含开启AOP支持;若使用纯Spring,需在配置类上添加@EnableAspectJAutoProxy-21。
5.3 编写切面类——方法耗时监控示例
@Component // 将切面类交给Spring管理 @Aspect // 标记这是一个切面类 public class TimeMonitorAspect { private static final Logger log = LoggerFactory.getLogger(TimeMonitorAspect.class); // 方式一:直接在通知注解中写切入点表达式 @Around("execution( com.example.service...(..))") public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { long begin = System.currentTimeMillis(); // 【关键】调用原始业务方法 Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); String methodName = joinPoint.getSignature().getName(); log.info("【AOP监控】方法 {} 执行耗时: {} ms", methodName, (end - begin)); return result; } }
-1
5.4 完整切面示例——包含五种通知
@Component @Aspect public class CompleteLogAspect { // 先定义可复用的切点 @Pointcut("execution( com.example.service...(..))") public void serviceMethod() {} @Before("serviceMethod()") public void logBefore(JoinPoint joinPoint) { System.out.println("【前置】调用方法:" + joinPoint.getSignature().getName()); } @AfterReturning(pointcut = "serviceMethod()", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("【返回】返回值:" + result); } @AfterThrowing(pointcut = "serviceMethod()", throwing = "ex") public void logException(JoinPoint joinPoint, Exception ex) { System.out.println("【异常】异常信息:" + ex.getMessage()); } @After("serviceMethod()") public void logFinally(JoinPoint joinPoint) { System.out.println("【最终】方法执行完毕"); } }
-21
5.5 执行流程说明
当调用被增强的业务方法时:
代理对象拦截方法调用
执行@Around通知的前置部分
执行@Before通知
执行目标方法(业务逻辑)
执行@AfterReturning(正常)或@AfterThrowing(异常)
执行@After通知
执行@Around通知的后置部分并返回结果
六、底层原理:动态代理机制
一句话原理:Spring AOP的底层依赖于动态代理技术,在运行时动态创建代理对象,通过代理对象拦截目标方法调用,并在方法调用前后插入切面逻辑-29-。
两种代理方式对比
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 前提条件 | 目标类必须实现至少一个接口 | 目标类无需实现接口 |
| 实现原理 | 基于反射,生成实现同一接口的代理类 | 基于字节码技术,生成目标类的子类 |
| 代理限制 | 只能代理接口中定义的方法 | 无法代理final类/final方法 |
| 性能 | 创建代理较慢,方法调用一般 | 创建代理较快,方法调用较优 |
| Spring默认策略 | 有接口时默认使用 | 无接口时自动切换 |
-29-30-39
💡 2026年新变化:Spring Boot从2.x版本开始已将CGLIB设为默认代理方式,Spring Boot 3.x延续这一策略,@EnableAspectJAutoProxy(proxyTargetClass = true)可强制使用CGLIB--。
代理创建决策流程图
目标类是否实现了接口? ├── 是 → 使用JDK动态代理(反射,基于接口) └── 否 → 使用CGLIB动态代理(字节码,生成子类)
七、典型应用场景
| 场景 | 说明 | 实现方式 |
|---|---|---|
| 日志记录 | 记录方法入参、出参、执行时间 | @Around环绕通知 |
| 声明式事务 | Spring @Transactional就是基于AOP实现的 | @Around事务开启/提交/回滚 |
| 权限校验 | 方法执行前校验用户身份 | @Before前置通知 |
| 性能监控 | 统计方法执行耗时、调用次数 | @Around环绕通知 |
| 缓存管理 | 方法执行前查缓存,执行后写缓存 | @Around环绕通知 |
-8-
八、高频面试题与参考答案
Q1:什么是AOP?Spring AOP的实现原理是什么?
参考答案:AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,通过横向抽取机制将日志、事务、权限等横切关注点从业务逻辑中分离出来。Spring AOP基于动态代理实现——目标类有接口时使用JDK动态代理(基于反射),无接口时使用CGLIB代理(生成子类),在运行时将切面逻辑织入目标方法。
💡 踩分点:说出AOP定义 + 两种代理方式 + 动态代理原理-39
Q2:JDK动态代理和CGLIB有什么区别?
参考答案:① 实现条件:JDK要求目标类实现接口,CGLIB无此限制;② 实现原理:JDK基于反射生成接口代理类,CGLIB基于字节码生成目标类子类;③ 限制:CGLIB无法代理final类和方法;④ 性能:CGLIB代理创建更快,方法调用性能更优;⑤ Spring默认:Spring Boot默认使用CGLIB,传统Spring MVC默认使用JDK。
💡 踩分点:接口要求 + 实现方式 + final限制 + 默认策略-30
Q3:AOP中@Around和其他通知有什么区别?
参考答案:@Around环绕通知是功能最强的通知类型,它可以控制目标方法是否执行、修改入参和返回值,而@Before/@After等无法干预目标方法的执行过程。@Around必须手动调用ProceedingJoinPoint.proceed()执行目标方法,并返回Object类型的结果。
💡 踩分点:控制能力差异 + proceed()调用-1
Q4:Spring AOP与AspectJ有什么区别?
参考答案:① 织入时机:Spring AOP是运行时增强(基于动态代理),AspectJ是编译时/类加载时增强;② 性能:AspectJ性能更好(编译时完成),Spring AOP略有开销;③ 支持范围:Spring AOP仅支持方法级连接点,AspectJ支持字段、构造器等更多连接点;④ 依赖:Spring AOP无需特殊编译器,AspectJ需要特定编译器。
💡 踩分点:织入时机 + 支持范围-
Q5:在Spring AOP中,同类内部方法调用为什么切面会失效?
参考答案:Spring AOP基于代理实现。当通过代理对象调用方法时,切面生效;但同类内部方法直接调用this.method()时,绕过了代理对象,直接调用目标对象本身,因此切面逻辑不会执行。解决方案:从IoC容器中获取自身代理对象,或使用AopContext.currentProxy()获取代理对象后调用。
九、总结
本文围绕Spring AOP核心知识体系,梳理了以下要点:
| 板块 | 核心要点 |
|---|---|
| AOP定义 | 面向切面编程,横向抽取机制,分离横切关注点 |
| OOP vs AOP | 纵向继承 vs 横向切入,AOP是OOP的补充 |
| 核心术语 | Aspect、Join Point、Pointcut、Advice、Target、Weaving |
| 五种通知 | @Before、@After、@AfterReturning、@AfterThrowing、@Around |
| 底层原理 | 动态代理(JDK反射 + CGLIB字节码),运行时织入 |
| 应用场景 | 日志、事务、权限、监控、缓存 |
⚠️ 常见易错点提醒
@Around必须调用proceed():否则目标方法不会执行
同类内部调用切面失效:this调用绕过代理对象
CGLIB无法代理final类/方法
切面类必须被Spring容器管理:加上
@Component或通过配置注册切入点表达式越精确越好:避免匹配过多方法影响性能
后续我们将深入AOP源码分析、Spring 6.x AOT编译优化以及Spring Boot 3.x中的AOP最佳实践,敬请期待。