北京时间 2026年4月9日
前言

AOP(Aspect Oriented Programming,面向切面编程)与 IoC 并称为 Spring 框架的两大核心技术支柱。如果说 IoC 解决了对象之间的耦合问题,那么 AOP 就解决了“横切逻辑”的复用难题——日志记录、性能监控、权限校验、事务管理等散落在业务代码中的功能,用 AOP 可以在不修改原始业务代码的前提下集中统一处理-2。很多开发者的现状是:项目中用到了 AOP 却讲不清底层原理,面试时被问到 JDK 动态代理与 CGLIB 的区别就答不上来。本文将从痛点出发,由浅入深地讲透 Spring AOP,帮助读者建立起从概念理解到原理掌握再到面试输出的完整知识链路。
一、痛点切入:为什么需要AOP?

先看一个典型场景。假设我们需要统计 Service 层每个方法的执行耗时,传统做法是直接在每个方法内部插入计时逻辑:
public User getUserById(Long id) { long start = System.currentTimeMillis(); // 业务逻辑 User user = userDao.selectById(id); long end = System.currentTimeMillis(); log.info("getUserById 耗时: {}ms", end - start); return user; }
这种做法的痛点非常明显:
代码重复:每个需要统计的方法都要写一遍计时逻辑,若系统有 50 个方法,就需要重复写 50 次
耦合度高:日志/监控代码与业务逻辑强耦合,修改日志规则需要改动每一个目标方法
维护困难:横切关注点(cross-cutting concerns)散落各处,想要统一调整格式或增加新功能,工作量呈线性增长
职责混乱:业务方法既要处理核心逻辑,又要兼顾外围辅助功能,违背单一职责原则
AOP 正是为了解决上述问题而诞生的。它将日志、事务、安全等“横切逻辑”从业务代码中抽离出来,形成一个独立的“切面”,在程序运行时通过代理机制动态植入到目标方法中,实现无侵入式增强-2。
二、核心概念讲解:什么是AOP?
定义:AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它将散布在各个业务方法中的横切逻辑抽取出来,形成独立的“切面”,在运行时动态织入目标方法,实现功能增强-2。
拆解关键词:
横切关注点:指那些影响多个类、散落在各处的公共行为,如日志、事务、安全校验
切面(Aspect) :对横切关注点的模块化封装,定义了“在何时、何地、做什么”
织入(Weaving) :将切面应用到目标对象并创建代理对象的过程
生活化类比:
把开发比作盖房子,OOP 负责搭建房屋的主体结构(墙、柱、屋顶),每一层的功能是纵向的。而 AOP 负责铺设全屋共用的水电管道,这些管道横穿所有房间——每个房间都需要电,但你不必在每个房间重复铺设一次电路,只需统一设计一套电路系统,在各个房间的“连接点”接上即可。AOP 中的切面就相当于这套电路系统,通知(Advice)则是各个房间的开关面板。
核心价值:
无侵入性:不修改原始业务代码
代码复用:横切逻辑集中管理,避免重复
灵活可控:通过配置即可调整增强规则
三、关联概念讲解:AOP核心术语与通知类型
3.1 五大核心术语
| 术语 | 定义 | 通俗理解 |
|---|---|---|
| 连接点(JoinPoint) | 程序执行过程中可被拦截的点(Spring 中即方法调用) | 所有房间的可安装点位 |
| 切入点(Pointcut) | 定义哪些连接点需要被拦截的规则表达式 | 指定哪些房间要装开关 |
| 通知(Advice) | 拦截到连接点后要执行的增强代码 | 开关按下后的具体动作 |
| 切面(Aspect) | 切入点 + 通知的封装单元 | 完整的电路设计方案 |
| 目标对象(Target) | 被代理增强的原始对象 | 被接入电器的房间 |
3.2 五类通知类型
Spring AOP 提供了丰富的通知机制,覆盖方法执行的各个阶段-1:
| 通知类型 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before | 目标方法执行前 |
| 后置通知 | @After | 目标方法执行后(无论是否抛异常) |
| 返回通知 | @AfterReturning | 目标方法正常返回后 |
| 异常通知 | @AfterThrowing | 目标方法抛异常时 |
| 环绕通知 | @Around | 包裹目标方法,可完全控制执行过程(功能最强) |
四、概念关系总结
一句话概括:AOP 是一种编程思想,而 Spring AOP 是这种思想的框架实现;切入点定义了“在哪里”执行增强,通知定义了“做什么”,切面则将二者封装为可复用的模块-1。
对比强化:
| 对比维度 | AOP(思想) | Spring AOP(实现) |
|---|---|---|
| 定位 | 编程范式,解决问题的方法论 | 具体框架,提供开箱即用的实现 |
| 织入时机 | 理论上支持编译期、类加载期、运行期 | 仅支持运行期织入(基于代理) |
| 性能 | 取决于具体实现 | 动态代理带来少量运行时开销 |
| 灵活性 | 抽象概念,无具体限制 | 仅支持方法级拦截 |
五、代码示例:3步实现方法耗时统计
下面通过一个完整的实战案例,演示 Spring AOP 的开发流程-2-40。
Step 1:引入依赖
<!-- Maven --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
Spring Boot 会自动启用 AOP 支持,无需额外配置。
Step 2:编写切面类
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j @Aspect // ① 标识该类为切面类 @Component // ② 将切面类纳入Spring容器管理 public class TimeAspect { // ③ @Around:环绕通知,可完全控制目标方法的执行 @Around("execution( com.example.service..(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { // 方法执行前:记录开始时间 long start = System.currentTimeMillis(); String methodName = pjp.getSignature().getName(); // 执行原始方法 Object result = pjp.proceed(); // 方法执行后:计算耗时 long cost = System.currentTimeMillis() - start; log.info("方法 {} 执行耗时: {}ms", methodName, cost); return result; } }
Step 3:运行效果
执行目标方法后,控制台自动输出耗时日志:
方法 getUserById 执行耗时: 2ms 方法 updateUserName 执行耗时: 1ms
对比新旧实现:
传统方式:每个方法内部重复写计时代码(50 个方法 → 50 份重复代码)
AOP 方式:仅在一个切面类中写一次计时逻辑,通过切入点表达式精准拦截目标方法,实现一处定义、多处生效
切入点表达式详解
上述示例中 execution( com.example.service..(..)) 的含义:
execution():切入点表达式标志第一个
:匹配任意返回值类型com.example.service:目标方法所在包.:匹配包下所有类.(..):匹配所有方法,..匹配任意参数
除了 execution,Spring AOP 还支持通过自定义注解指定切入点,这种方式更加灵活,可以精确控制哪些方法需要被拦截-40:
// 定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable {} // 切面中使用 @Pointcut("@annotation(com.example.annotation.Loggable)") public void loggablePointcut() {} // 在需要拦截的方法上添加 @Loggable 即可
六、底层原理与技术支撑
6.1 动态代理:Spring AOP的底层基石
Spring AOP 的实现依赖于动态代理机制-1-30。当目标对象被 AOP 增强时,Spring 不会直接使用原始对象,而是在运行时动态生成一个代理对象。客户端实际调用的是代理对象,代理对象在方法调用前后插入增强逻辑(通知),再调用原始对象的方法。
6.2 两种代理方式对比
Spring AOP 提供了两种代理方式,根据目标对象是否实现接口自动选择-31-30:
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 底层技术 | Java 反射机制(java.lang.reflect.Proxy + InvocationHandler) | 字节码操作库 ASM,动态创建子类 |
| 实现方式 | 动态创建实现了目标接口的代理类 | 动态创建目标类的子类,重写父类方法 |
| 要求 | 目标类必须实现至少一个接口 | 无需实现接口 |
| 性能特点 | 代理对象创建快,方法调用性能较高 | 代理对象创建较慢(约 JDK 的 8 倍耗时),但方法调用性能更高(约提升 10 倍) |
| 局限性 | 无法代理没有接口的类 | 无法代理 final 类或 final 方法 |
| 适用场景 | 目标类有接口且对创建速度敏感 | 目标类无接口或单例对象(创建一次、多次调用) |
6.3 默认代理策略
不同环境下的默认代理方式存在差异:
Spring MVC 传统环境:默认使用 JDK 动态代理
Spring Boot 环境:默认使用 CGLIB 代理-
6.4 执行流程
代理创建:Spring 启动时,
@EnableAspectJAutoProxy开启 AOP 功能,通过BeanPostProcessor在 Bean 初始化后扫描切面代理选择:
DefaultAopProxyFactory根据目标类是否实现接口,决定使用 JDK 还是 CGLIB 代理方法拦截:客户端调用代理对象的方法 → 代理对象拦截调用 → 通过
ReflectiveMethodInvocation责任链依次执行各通知原始方法执行:责任链最后调用原始目标方法
底层依赖知识定位:以上涉及的核心底层技术包括 Java 反射、字节码操作(ASM)、类加载机制等。深入源码可参考 JdkDynamicAopProxy.invoke() 和 CglibAopProxy 的实现,为后续进阶学习预留空间。
七、高频面试题与参考答案
题目1:什么是 AOP?Spring AOP 是如何实现的?
参考答案:
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它将日志、事务等横切关注点从业务逻辑中抽离出来,形成独立切面,在运行时动态织入目标方法,实现无侵入式增强。
Spring AOP 的底层实现基于动态代理:若目标类实现了接口,默认使用 JDK 动态代理(基于 java.lang.reflect.Proxy 和 InvocationHandler);若未实现接口,则使用 CGLIB 动态创建目标类的子类来实现代理-1-30。
踩分点:AOP 的定义 + 动态代理 + JDK/CGLIB 两种方式
题目2:JDK 动态代理和 CGLIB 动态代理的区别?
参考答案:
| 区别维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口代理 | 基于子类代理 |
| 依赖 | 被代理类必须实现接口 | 被代理类可为普通类(非 final) |
| 底层 | Java 反射 | ASM 字节码技术 |
| 性能 | 创建快,调用略慢 | 创建慢,调用快 |
| 适用 | 有接口的目标类 | 无接口或需代理类的场景 |
Spring Boot 2.0 后默认使用 CGLIB 代理-31。
踩分点:接口 vs 类、反射 vs 字节码、两种方式各自的适用场景
题目3:AOP 有哪些通知类型?分别用什么注解?
参考答案:
Spring AOP 提供 5 种通知类型-1-41:
@Before(前置):目标方法执行前
@After(后置):目标方法执行后(无论是否异常)
@AfterReturning(返回):目标方法正常返回后
@AfterThrowing(异常):目标方法抛异常后
@Around(环绕):最强大,可完全控制方法执行,需调用
proceed()执行目标方法
踩分点:5 种类型 + 各自的执行时机 + @Around 的特殊性
题目4:Spring AOP 和 AspectJ 的关系?
参考答案:
Spring AOP 和 AspectJ 都是 AOP 的实现框架。区别在于:
AspectJ:是独立的 AOP 框架,支持编译期、类加载期和运行期织入,功能更强大,支持字段拦截等细粒度切点
Spring AOP:基于 Spring 框架实现,仅支持运行期织入(动态代理),只支持方法级拦截,使用 AspectJ 的注解语法(如 @Aspect、@Pointcut)但底层机制不同
Spring 选择集成 AspectJ 的注解语法,是为了降低学习成本,但底层实现仍是动态代理,而非 AspectJ 的字节码织入-。
踩分点:独立框架 vs Spring 集成实现 + 织入时机差异
题目5:Spring AOP 中为什么同一个类内部方法调用 AOP 会失效?
参考答案:
因为 Spring AOP 基于代理实现,内部方法调用是通过 this 直接调用原始对象的方法,绕过了代理对象,因此不会触发切面增强。解决方案:
将调用方法抽取到不同 Bean 中
通过
AopContext.currentProxy()获取当前代理对象进行调用(需配置exposeProxy=true)-52
踩分点:代理机制导致的 this 调用问题 + 两种解决方案
八、结尾总结
核心知识点回顾
| 学习层面 | 关键内容 |
|---|---|
| 概念理解 | AOP 解决横切逻辑与业务逻辑的耦合问题,核心是抽离、封装、织入 |
| 核心术语 | 切面 = 切入点 + 通知;连接点是可拦截点;切入点是筛选规则 |
| 代码实战 | 3 步实现:引入依赖 → 编写切面类(@Aspect + @Component)→ 定义通知 |
| 底层原理 | 动态代理(JDK 基于接口反射 + CGLIB 基于字节码创建子类) |
| 高频考点 | AOP 定义、JDK vs CGLIB、5 种通知、内部调用失效、与 AspectJ 关系 |
重点强调
AOP 是思想,Spring AOP 是框架实现,动态代理是技术手段——理清这三层关系,就能从本质上理解 Spring AOP
Spring Boot 中 AOP 默认使用 CGLIB 代理,这与传统 Spring MVC 环境的 JDK 代理默认策略不同,面试时需注意区分
@Around 是功能最全的通知类型,可以完全控制方法执行,但需注意
proceed()必须执行一次,否则目标方法不会调用
进阶预告
下一篇将深入讲解 Spring AOP 源码级剖析,带你追踪从 @EnableAspectJAutoProxy 到 DefaultAopProxyFactory 再到 JdkDynamicAopProxy.invoke() 的完整调用链路,彻底搞懂 Spring AOP 的底层实现。欢迎持续关注!
