更新时间:北京时间2026年4月9日
开篇引入

在Spring框架的两大核心支柱中,如果说IoC(控制反转,Inversion of Control)解决了对象管理的问题,那么AOP(Aspect-Oriented Programming,面向切面编程)则解决了横切关注点的统一处理难题。据统计,2025年Java生态中已有78%的企业级应用使用AOP解决横切关注点问题,传统面向对象编程(OOP,Object-Oriented Programming)在日志、事务等场景下的代码重复率高达60%以上-2。很多人虽然日常在用@Transactional、@Around,却说不清AOP底层的动态代理机制——面试时被问到“JDK动态代理和CGLIB有什么区别”就卡壳。本文将从痛点驱动→核心概念→底层原理→代码实战→面试考点五个层面,带你把Spring AOP学透。
一、痛点切入:为什么需要AOP?

先看一个典型的业务场景:假设我们要为一个用户服务方法添加日志记录、事务管理和权限校验。
传统OOP实现方式:
public class UserServiceImpl implements UserService { public void saveUser(User user) { // 日志记录 System.out.println("开始执行saveUser方法"); // 权限校验 if (!hasPermission()) throw new SecurityException(); // 业务逻辑 System.out.println("保存用户:" + user.getName()); // 事务提交 commitTransaction(); // 日志记录 System.out.println("saveUser方法执行结束"); } }
这种方式存在的问题:
代码冗余:每个业务方法都要重复编写日志、事务等代码,重复率极高
耦合度高:非业务代码侵入业务逻辑,修改日志格式要改所有方法
扩展性差:新增功能(如性能监控)需要修改所有相关方法
维护困难:横切关注点散落在各处,难以统一管理
AOP正是为解决这些问题而生——将横切关注点从业务逻辑中抽离,实现解耦与复用。
二、核心概念讲解(AOP核心术语)
什么是AOP?
AOP全称Aspect-Oriented Programming(面向切面编程),是一种编程范式。其核心思想是:将程序中的横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,作为独立模块进行处理-5。
用生活场景来理解:
把AOP想象成快递驿站。你寄快递时只需要把包裹交给驿站(业务逻辑),驿站在你不知情的情况下完成验视、打包、录入系统(横切逻辑)——你只关心包裹能不能寄到,中间的流程由驿站统一处理。
核心概念拆解
| 术语 | 英文 | 通俗解释 |
|---|---|---|
| 切面 | Aspect | 横切关注点的模块化,如日志切面、事务切面 |
| 连接点 | Join Point | 可以插入切面逻辑的位置(通常是方法执行) |
| 通知 | Advice | 切面在连接点上执行的具体动作 |
| 切点 | Pointcut | 通过表达式匹配一组连接点,决定哪些方法被增强 |
| 目标对象 | Target Object | 被代理的原始业务对象 |
| 织入 | Weaving | 将切面代码与目标对象关联并生成代理对象的过程 |
通知(Advice)的5种类型
| 注解 | 触发时机 | 典型用途 |
|---|---|---|
@Before | 目标方法执行前 | 参数校验、权限检查 |
@After | 目标方法执行后(无论是否异常) | 资源清理 |
@AfterReturning | 目标方法正常返回后 | 修改返回值、记录成功日志 |
@AfterThrowing | 目标方法抛出异常后 | 统一异常处理 |
@Around | 环绕目标方法执行,可完全控制 | 性能监控、事务控制(最强大) |
三、关联概念讲解:JDK动态代理 vs CGLIB
Spring AOP的核心实现依赖于动态代理机制,主要包含两种方式:
JDK动态代理
定义:JDK原生支持的动态代理方案,要求目标对象必须实现至少一个接口
核心类:
java.lang.reflect.Proxy和InvocationHandler原理:运行时生成一个实现目标接口的代理类,将方法调用转发到
InvocationHandler.invoke()-1
CGLIB动态代理
定义:CGLIB(Code Generation Library)是一个开源的字节码生成库,被Spring重新打包在
spring-core中原理:通过继承目标类生成子类代理,覆盖父类方法并织入增强逻辑-1
限制:
final类无法被代理,final方法和private方法无法被增强-21
四、概念关系与区别总结
JDK Proxy vs CGLIB 对比
| 维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现方式 | 基于接口 | 基于继承 |
| 是否要求接口 | 必须实现接口 | 不需要接口 |
| 代理范围 | 仅代理接口中声明的方法 | 代理所有可继承的public方法 |
| 生成方式 | JDK内置,反射调用 | 字节码生成,直接调用 |
| 性能 | JDK 8+版本下两者差距缩小 | 执行速度略快 |
一句话总结
JDK动态代理是基于接口的代理,CGLIB是基于继承的代理——前者依赖接口,后者依赖父类。
Spring中的代理选择策略
Spring Framework(非Boot):默认策略——目标对象有接口用JDK,无接口用CGLIB-21
Spring Boot 2.0及以上:默认使用CGLIB代理-41
强制使用CGLIB:设置
spring.aop.proxy-target-class=true或@EnableAspectJAutoProxy(proxyTargetClass = true)-21
五、代码示例演示
步骤1:添加依赖(Spring Boot项目)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
步骤2:创建切面类
@Aspect @Component public class LogAspect { // 定义切点:匹配service包下所有类的所有方法 @Pointcut("execution( com.example.service..(..))") public void serviceMethod() {} // 前置通知:记录方法开始执行 @Before("serviceMethod()") public void logBefore(JoinPoint joinPoint) { System.out.println("方法 " + joinPoint.getSignature().getName() + " 开始执行"); } // 环绕通知:统计方法执行时间 @Around("serviceMethod()") public Object measureTime(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); // 执行目标方法 long elapsed = System.currentTimeMillis() - start; System.out.println(pjp.getSignature() + " 耗时:" + elapsed + "ms"); return result; } }
执行流程说明:
Spring启动时扫描到
@Aspect注解的类根据代理策略为匹配的目标Bean生成代理对象
调用目标方法时,代理对象先执行通知逻辑
通知中通过
pjp.proceed()调用原方法方法执行完毕后返回结果,继续执行后置逻辑
六、底层原理与技术支撑
AOP底层依赖的核心技术
| 技术 | 作用 |
|---|---|
| 反射(Reflection) | JDK动态代理通过反射调用目标方法 |
| 代理模式(Proxy Pattern) | 整个AOP的设计思想基础 |
| BeanPostProcessor | Spring容器在Bean初始化后织入代理- |
| ASM字节码框架 | CGLIB底层使用ASM操作字节码生成代理类 |
代理生成的核心流程
Spring容器启动,解析
@Aspect注解的切面类切点表达式匹配目标Bean的方法
BeanPostProcessor在Bean初始化后介入根据代理策略创建代理对象(
JdkDynamicAopProxy或CglibAopProxy)将代理对象放入容器,替代原始Bean
七、高频面试题与参考答案
1. 什么是AOP?Spring AOP是如何实现的?
标准答案: AOP(面向切面编程)是一种编程范式,能在不修改业务代码的情况下为方法统一添加横切逻辑(如日志、事务)。Spring AOP基于动态代理实现:目标类有接口时使用JDK动态代理,无接口时使用CGLIB代理生成子类,容器最终注入的是代理对象而非原始对象-35。
踩分点: 横切关注点 + 动态代理 + JDK/CGLIB选择规则 + 代理对象注入
2. JDK动态代理和CGLIB有什么区别?
标准答案: JDK代理基于接口,要求目标类实现接口,通过InvocationHandler转发调用;CGLIB基于继承,通过生成目标类的子类实现,不要求接口但无法代理final类和方法。性能上,JDK 8+版本后两者差距已不明显,Spring Boot 2.0+默认使用CGLIB-44。
3. @Transactional为什么有时会失效?
标准答案: 常见原因有三:①方法不是public(事务只作用于public方法);②同类中内部调用(没有经过代理对象,AOP不生效);③final方法无法被代理-35。
4. Spring AOP和AspectJ有什么区别?
标准答案: Spring AOP是运行时动态代理,仅支持方法级连接点,配置简单适合业务场景;AspectJ支持编译时/类加载时织入,支持字段、构造器等更丰富的连接点,功能强大但较重。Spring AOP可复用AspectJ的注解语法-1。
5. @Around和@Before/@After的核心区别是什么?
标准答案: @Before/@After只能分别在方法前后执行逻辑,无法控制方法是否执行;@Around可通过ProceedingJoinPoint的proceed()完全控制方法执行时机和次数,是最强大的通知类型-35。
八、结尾总结
核心知识点回顾
AOP本质:将横切关注点从业务逻辑中抽离,实现解耦与复用
核心概念:切面、通知、切点、连接点——记住“切面里定义通知,切点指定匹配哪些连接点”
底层实现:JDK动态代理(基于接口)+ CGLIB动态代理(基于继承)
代理选择:Spring Boot 2.0+默认CGLIB,Spring Framework默认有接口用JDK
进阶建议
理解
BeanPostProcessor在AOP代理生成中的角色掌握切入点表达式的灵活配置(
execution、@annotation、within等)深入阅读
JdkDynamicAopProxy和CglibAopProxy源码
下一篇我们将深入Spring事务管理的底层原理,讲解@Transactional的执行机制和传播行为,敬请关注!
本系列文章持续更新中,欢迎点赞收藏,有问题欢迎在评论区交流讨论。
扫一扫微信交流