2026年4月10日更新 · 全文约5000字,阅读时间15分钟

一、开篇引入

很多开发者的学习痛点也很集中:日常开发中会用@Aspect注解写切面,却不清楚底层动态代理如何工作;面试被问到“Spring AOP和AspectJ有什么区别”时,逻辑混乱、答不到要点。

本文将从

二、痛点切入:为什么需要AOP?
2.1 传统实现方式的问题
假设你正在开发一个电商系统,登录、下单、支付、查询等业务方法,每个都需要添加日志打印、权限校验、性能监控。传统OOP(Object Oriented Programming,面向对象编程)的做法如下:
public class OrderService { // 下单方法——手动添加了3种增强逻辑,代码严重膨胀 public void placeOrder(Order order) { // 1. 日志打印 System.out.println("开始下单:" + order); // 2. 权限校验 if (!checkPermission("placeOrder")) return; // 3. 性能监控 long start = System.currentTimeMillis(); try { // 核心业务逻辑 doPlaceOrder(order); } finally { // 性能监控收尾 System.out.println("执行耗时:" + (System.currentTimeMillis() - start) + "ms"); } } }
2.2 传统方式的三大致命缺陷
这种实现方式存在三个严重问题:
代码重复:日志、权限、监控逻辑在每个方法中都要写一遍,10个方法就是10份重复代码-1。
耦合度高:核心业务逻辑与增强逻辑混杂在一起,改动日志格式需要修改所有业务类。
扩展性差:每新增一种增强(如加缓存),都要在所有方法中逐一修改,极易遗漏。
2.3 AOP的解决方案
AOP的设计初衷正是解决上述问题:将日志、事务、权限等横切逻辑(Cross-Cutting Concerns)从业务代码中剥离出来,封装成独立的“切面”,由框架在运行时自动织入到目标方法中,实现业务代码与增强逻辑的彻底解耦-1。
三、核心概念讲解:切面(Aspect)
3.1 标准定义
Aspect(切面) 是指封装了横切关注点的模块化类,通常包含多个通知(Advice) 和切点(Pointcut) 定义-29。简单说,切面就是“要增强的功能模块”,比如日志切面、事务切面、权限校验切面-1。
3.2 生活化类比
把AOP想象成高铁安检系统:
安检过程本身不是乘车业务,但每个乘客进站都要经过。
安检口就是“切面”,安检流程是“通知”,进站闸机是“连接点”,持票乘客是“目标对象”。
3.3 作用与价值
切面将横切逻辑集中管理,改动切面类中的一处代码,就能影响所有被织入的目标方法,大幅提升代码的可维护性和可复用性-1。
四、关联概念讲解:连接点、切点、通知、织入
4.1 Join Point(连接点)
Join Point(连接点) 是指程序运行中可以插入切面逻辑的特定位置,如方法调用、异常抛出等-29。在Spring AOP中,连接点特指方法的执行。
4.2 Pointcut(切点)
Pointcut(切点) 是一组匹配连接点的表达式规则——即从所有可增强的方法中,筛选出真正需要增强的那些方法-1。
所有连接点 = {UserService.login, UserService.register, OrderService.create, ...} 切点 = {UserService.login, OrderService.create} // 只增强这两个
4.3 Advice(通知)
Advice(通知) 定义了增强逻辑在什么时候执行。Spring AOP提供了五种通知类型-29-1:
| 通知类型 | 注解 | 执行时机 | 典型场景 |
|---|---|---|---|
| 前置通知 | @Before | 目标方法执行前 | 参数校验、权限校验 |
| 后置通知 | @After | 目标方法执行后(无论是否异常) | 资源清理 |
| 返回后通知 | @AfterReturning | 目标方法正常返回后 | 记录返回值、缓存更新 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常后 | 异常监控、日志记录 |
| 环绕通知 | @Around | 完全包裹目标方法 | 性能监控、事务控制 |
关键注意:@Around环绕通知需要手动调用proceed()来执行原始业务方法,且返回值必须声明为Object;其他通知类型则不需要关注这些细节-1。
4.4 Weaving(织入)
Weaving(织入) 是指将切面逻辑应用到目标对象,生成代理对象的过程-1。Spring AOP采用运行时织入——在程序运行期间通过动态代理生成代理对象,而非编译时织入。
五、概念关系与区别总结
一句话记忆:切面(Aspect)= 切点(Pointcut)+ 通知(Advice)。
核心概念关系图:
Join Point(所有可增强的方法) ↓ 通过Pointcut表达式筛选 Pointcut(需要增强的方法子集) ↓ 加上Advice(何时执行增强逻辑) Aspect(切面 = 切点 + 通知) ↓ 通过Weaving织入 Target Object → Proxy Object(代理对象)
| 概念 | 英文 | 一句话解释 |
|---|---|---|
| 切面 | Aspect | 增强功能模块 |
| 连接点 | Join Point | 可增强的位置(方法调用) |
| 切点 | Pointcut | 真正要增强的方法集合 |
| 通知 | Advice | 增强逻辑的执行时机 |
| 织入 | Weaving | 把切面应用到目标的过程 |
六、代码示例实战
6.1 添加依赖(Spring Boot 3.x)
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
6.2 定义切面类:统一日志与性能监控
// 1. 定义自定义注解(可选,实现精细化匹配) @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable { } // 2. 切面类 @Component @Aspect // 标记这是一个切面类 @Slf4j public class LoggingAspect { // 方式一:直接将切点表达式写在通知注解中 @Around("execution( com.example.service..(..))") public Object recordPerformance(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); String methodName = joinPoint.getSignature().toShortString(); // 前置增强:打印开始日志 log.info("【AOP】方法开始执行:{}", methodName); // 调用原始业务方法(必须手动调用,否则业务不执行) Object result = joinPoint.proceed(); // 后置增强:打印耗时 long elapsed = System.currentTimeMillis() - start; log.info("【AOP】方法执行完成:{},耗时:{} ms", methodName, elapsed); return result; } // 方式二:先定义切点,再引用 @Pointcut("@annotation(com.example.annotation.Loggable)") public void loggableMethods() {} @Before("loggableMethods()") public void logBefore(JoinPoint joinPoint) { log.info("【前置通知】即将执行:{}", joinPoint.getSignature().getName()); } }
6.3 业务类:完全无侵入
@Service public class OrderService { @Loggable // 使用自定义注解标记需要增强的方法 public void placeOrder(Order order) { // 核心业务逻辑——没有任何AOP相关代码! System.out.println("正在处理订单:" + order); } }
6.4 执行流程说明
当调用orderService.placeOrder(order)时,Spring AOP的执行链路如下:
容器返回的是代理对象而非原始
OrderService对象。代理对象根据切点匹配规则,识别出
placeOrder方法需要被增强。执行
@Around环绕通知的前置部分(打印开始日志)。调用
proceed()执行原始业务方法。执行
@Around环绕通知的后置部分(打印耗时)和@Before前置通知。返回最终结果-2。
七、底层原理与技术支撑
7.1 两种动态代理机制
Spring AOP的底层核心是动态代理技术。Spring根据目标对象的情况自动选择代理方式--11:
| 代理方式 | 实现原理 | 适用条件 | 性能特点 |
|---|---|---|---|
| JDK动态代理 | 基于Java反射,通过Proxy类创建实现了目标接口的代理类 | 目标对象实现了至少一个接口 | 生成代理快,执行稍慢 |
| CGLIB动态代理 | 基于ASM字节码框架,生成目标类的子类作为代理 | 目标对象未实现接口(或强制使用CGLIB) | 生成代理稍慢,执行更快 |
核心限制:CGLIB通过继承目标类生成子类代理,因此final修饰的类和方法无法被代理;private方法也无法被增强-11。
7.2 底层依赖知识点
AOP的实现高度依赖以下底层技术:
反射机制:JDK动态代理的核心基础。
字节码操作:CGLIB依赖ASM框架在运行时生成新的字节码-。
责任链模式:多个通知的执行通过拦截器链(Interceptor Chain)串联,依次调用-2。
7.3 Spring AOP vs AspectJ
| 对比维度 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时(动态代理) | 编译时 / 类加载时 |
| 连接点范围 | 仅方法级别 | 字段、构造器、静态代码块等 |
| 容器要求 | 只能代理Spring管理的Bean | 不依赖Spring容器 |
| 配置复杂度 | 简单(注解或XML) | 较复杂(需引入ajc编译器) |
| 适用场景 | 轻量级方法拦截 | 复杂AOP需求 |
一句话总结:Spring AOP是运行时织入的轻量级实现,更简单易用;AspectJ是功能更强大的编译时增强框架,两者可互补使用-22-29。
八、高频面试题与参考答案
Q1:什么是Spring AOP?和OOP有什么区别?
参考答案:
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,通过动态代理技术,在不修改原有业务代码的前提下,将日志、事务、权限等横切逻辑统一织入到目标方法中-。OOP关注纵向的继承与封装,以类为模块单元;AOP关注横向的切入,将跨多个模块的通用逻辑抽取为切面,两者是互补关系,而非替代-。
踩分点:AOP定义 + 动态代理 + 横切逻辑 + 与OOP的互补关系
Q2:Spring AOP的底层实现原理是什么?JDK动态代理和CGLIB有什么区别?
参考答案:
Spring AOP基于动态代理机制,在运行时为目标对象生成代理对象。JDK动态代理基于Java反射,要求目标类实现至少一个接口,通过InvocationHandler拦截方法调用-。CGLIB通过ASM字节码框架生成目标类的子类作为代理,无需接口即可工作-。Spring默认:目标有接口时用JDK代理,无接口时用CGLIB代理-11。
踩分点:动态代理 + JDK代理条件/原理 + CGLIB原理 + Spring默认策略
Q3:Spring AOP中有哪些通知类型?@Around通知需要注意什么?
参考答案:
五种通知类型:@Before(前置)、@After(后置,无论异常)、@AfterReturning(正常返回后)、@AfterThrowing(异常时)、@Around(环绕)-29。@Around最强大,需要手动调用ProceedingJoinPoint.proceed() 执行原始业务方法,返回值必须声明为Object,否则会丢失原始返回结果-1。
踩分点:5种类型逐一列举 + @Around的特殊性(proceed + Object返回值)
Q4:Spring AOP和AspectJ有什么区别?如何选择?
参考答案:
Spring AOP是运行时动态代理织入,只支持方法级别的连接点,只能代理Spring容器管理的Bean;AspectJ是编译时或类加载时织入,支持字段、构造器等更多连接点类型,可代理任何Java对象。选型建议:只需对Spring Bean进行方法拦截时用Spring AOP(配置简单);需要更丰富的切面功能或代理非Spring对象时用AspectJ--22。
踩分点:织入时机差异 + 连接点范围差异 + 容器依赖差异 + 选型建议
Q5:Spring AOP为什么无法代理同一个类中的内部方法调用?
参考答案:
因为Spring AOP基于代理模式,只能拦截通过代理对象发起的调用。当目标对象内部直接调用this.method()时,绕过了代理对象,直接调用原始对象的方法,因此切面逻辑不会执行。解决方案:通过AopContext.currentProxy()获取当前代理对象,或者将内部方法抽离到另一个Bean中注入使用-46。
踩分点:代理模式本质 + this调用绕过代理 + 两种解决思路
九、结尾总结
核心知识点回顾
AOP核心概念:切面(Aspect)= 切点(Pointcut)+ 通知(Advice),连接点是被增强的可能位置,织入是将切面应用到目标的过程。
底层原理:基于JDK动态代理和CGLIB两种代理机制,运行时生成代理对象实现方法拦截。
五种通知类型:
@Before、@After、@AfterReturning、@AfterThrowing、@Around,@Around最强大但需手动调用proceed()。Spring AOP vs AspectJ:Spring AOP是运行时轻量级方案,AspectJ是编译时全功能框架。
重点与易错点提醒
切面类必须同时添加
@Aspect和@Component注解,否则Spring无法识别。@Around环绕通知中必须调用proceed(),否则原始业务方法不会执行。同一个类中的内部方法调用不会触发AOP增强,因为绕过了代理对象。
CGLIB代理无法代理
final类和final方法,无法增强private方法。
进阶预告
下一篇我们将深入Spring AOP的拦截器链(Interceptor Chain) 实现原理,剖析@Around通知的执行顺序与责任链模式在Spring AOP中的实际应用,敬请期待!
本文基于Spring Framework 6.x / Spring Boot 3.x撰写。文中所有代码示例均已验证可运行。如有疑问,欢迎留言交流。